blox'n'balls NG
Mal etwas praktisches...

Während ich mich zuletzt um Sachen gekümmert habe die dem Spiel nichts hinzufügen, habe ich mich jetzt um die Verbesserung der Steuerung gekümmert und ein wenig an den States innerhalb des Spiels gebastelt. So kann ich nun der Klasse "Ball" die maximale Anzahl der Bälle übergeben und die Methode Ball.update() gibt mir zurück ob alle Bälle verspielt sind oder nicht. Die verbleibende Anzahl der Bälle wird nun angezeigt und wenn kein Ball mehr verfügbar ist wird ein wahnsinnig schlechtes MP3 Sample abgespielt.
Das alles wird so nicht in bnb implementiert, es sind alles Sachen die das Framework auf die Sachen die da kommen vorbereiten. Wichtig war jetzt dass man mit dem Paddle den Ball exakter steuern kann und dass es möglich ist ein Spielende zu definieren.
Abschliessend noch ein Link zum aktuellen Status in Flash: http://www.sedm.de/monkey/bnb/flash/
Das alles wird so nicht in bnb implementiert, es sind alles Sachen die das Framework auf die Sachen die da kommen vorbereiten. Wichtig war jetzt dass man mit dem Paddle den Ball exakter steuern kann und dass es möglich ist ein Spielende zu definieren.
Abschliessend noch ein Link zum aktuellen Status in Flash: http://www.sedm.de/monkey/bnb/flash/
Partikel

Ein wichtiger Aspekt von b'n'b waren immer die Partikel. Ich habe deshalb nun zwei Klassen ("ParticleEmitter" und "Particle") implementiert um das in der NG Version fortzusetzen. Partikel werden stets über den Emitter gesteuert, es ist nicht anzuraten Methoden der Klasse "Particle" direkt zu verwenden.
So erzeugt man einen neuen Emitter ganz klassisch mit New() und übergibt ihm die ImageRef des ResourceManagers und die Position des Emitters. Zur Laufzeit darf die Position jederzeit mit der Methode "setPosition()" geändert werden.
Das nicht direkt ein Image, sondern ein Object vom Typ "ImageRef" übergeben wird hat den Grund dass die Targets "HTML5" und "Flash" nicht immer gleich alle Daten zur Verfügung haben. So muss man warten bis alle Bilder geladen wurden. Das Modul "class_particle.monkey" stellt somit selbst sicher dass erst etwas gezeichnet wird wenn alle Daten verfügbar sind.
Der Emitter erzeugt kontinuierlich Partikel. Das Verhalten der Partikel wird nach dem Erzeugen des Emitters festgelegt, kann aber zur Laufzeit geändert werden. Derzeit kann man die Beschleunigung (Velocity), die Streuung (Scattering) bestimmen sowie festlegen ob die Partikel in ihrer Lebenszeit ein- oder ausgeblendet werden sollen. Es fehlt noch die Möglichkeit die Partikel zu skalieren oder das Fading zu kombinieren (bspw. das Partikel eingeblendet und zu ihrem Lebensende wieder ausgeblendet werden). Zudem fehlt noch die Möglichkeit am Lebensende oder zu einer bestimmten Zeit neue Partikel zu erzeugen. Bspw. ein Feuer dessen Partikel am Lebensende zu Rauch werden etc.
Monkey zwingt uns leider dazu das Update und das Rendering der Partikel zu trennen, was bedeutet dass die Liste der Partikel doppelt iteriert werden muss. Außerdem ist es nicht möglich ein SingeSurface System zu programmieren weil einige Targets das nicht unterstützen (Mojo von Haus aus überhaupt nicht). So muss man aufpassen dass man nicht zuviele Partikel erzeugt. Aus diesem Grund versuche ich ein Tweening zu implementieren welches es ermöglicht mehr Partikel zu suggerieren als eigentlich zu sehen sind. Derzeit sind es einfach nur gleichgroße Images die positioniert und mit einem Alpha-Wert versehen werden.
Als Demo habe ich die letzte Demo verwendet und im iDS Screen einen Partikel-Effekt angewendet der am Mauszeiger hängt.
HTML5 Demo
Flash Demo
Als nächstes wird die Particle Klasse dann Gravitation, einen Boden (zum erneuten abprallen der Partikel) und Forces unterstützen. Desweiteren werde ich den ResourceManager und die Partikel Geschichte zu einem Modul kombinieren so dass das jeder Interessierte auch für seine Projekte nutzen kann.
So erzeugt man einen neuen Emitter ganz klassisch mit New() und übergibt ihm die ImageRef des ResourceManagers und die Position des Emitters. Zur Laufzeit darf die Position jederzeit mit der Methode "setPosition()" geändert werden.
Das nicht direkt ein Image, sondern ein Object vom Typ "ImageRef" übergeben wird hat den Grund dass die Targets "HTML5" und "Flash" nicht immer gleich alle Daten zur Verfügung haben. So muss man warten bis alle Bilder geladen wurden. Das Modul "class_particle.monkey" stellt somit selbst sicher dass erst etwas gezeichnet wird wenn alle Daten verfügbar sind.
Der Emitter erzeugt kontinuierlich Partikel. Das Verhalten der Partikel wird nach dem Erzeugen des Emitters festgelegt, kann aber zur Laufzeit geändert werden. Derzeit kann man die Beschleunigung (Velocity), die Streuung (Scattering) bestimmen sowie festlegen ob die Partikel in ihrer Lebenszeit ein- oder ausgeblendet werden sollen. Es fehlt noch die Möglichkeit die Partikel zu skalieren oder das Fading zu kombinieren (bspw. das Partikel eingeblendet und zu ihrem Lebensende wieder ausgeblendet werden). Zudem fehlt noch die Möglichkeit am Lebensende oder zu einer bestimmten Zeit neue Partikel zu erzeugen. Bspw. ein Feuer dessen Partikel am Lebensende zu Rauch werden etc.
Monkey zwingt uns leider dazu das Update und das Rendering der Partikel zu trennen, was bedeutet dass die Liste der Partikel doppelt iteriert werden muss. Außerdem ist es nicht möglich ein SingeSurface System zu programmieren weil einige Targets das nicht unterstützen (Mojo von Haus aus überhaupt nicht). So muss man aufpassen dass man nicht zuviele Partikel erzeugt. Aus diesem Grund versuche ich ein Tweening zu implementieren welches es ermöglicht mehr Partikel zu suggerieren als eigentlich zu sehen sind. Derzeit sind es einfach nur gleichgroße Images die positioniert und mit einem Alpha-Wert versehen werden.
Als Demo habe ich die letzte Demo verwendet und im iDS Screen einen Partikel-Effekt angewendet der am Mauszeiger hängt.
HTML5 Demo
Flash Demo
Als nächstes wird die Particle Klasse dann Gravitation, einen Boden (zum erneuten abprallen der Partikel) und Forces unterstützen. Desweiteren werde ich den ResourceManager und die Partikel Geschichte zu einem Modul kombinieren so dass das jeder Interessierte auch für seine Projekte nutzen kann.
States & Resources

In Monkey hat man oft das Problem dass man nicht weiß ob schon alle Daten geladen sind die man benötigt, darum habe ich den ResourceManager von clevijoki so umgebaut dass er Strict ist und als Module verwendbar ist. Daneben habe ich den Statemanager implementiert der die richtige Klasse zur rechten Zeit aufruft. Jeder Zustand des Programms ist eine eigene Klasse (die einzelnen Logos des Intros, das Hauptmenü, das Spiel selbst und es kommen wohl noch ein paar dazu).
Der Flow sieht derzeit so aus: iDS Logo (2sec oder MouseHit/Touch) -> SEDM Logo (2sec oder MouseHit/Touch) -> bnb Logo (2sec oder MouseHit/Touch) -> MainMenu -> Game <- MainMenu -> Game etc.
Das MainMenu und der Game Screen sind noch ohne Funktion, der Switch ist ein einfaches MouseHit oder ein Touch.
Die Targets Android und GLFW sorgen noch ein bisschen für Ärger. Unter Android werden die Fade timings nicht eingehalten (kann daran liegen dass die Images in FullHD vorliegen) und unter GLFW gehts garnicht (kann daran liegen dass die Images nicht in einer Grösse ^2 vorliegen. Wird noch geklärt.
Hier könnt ihr die Demo für HTML5 anschauen.
Die Grafiken sind allesamt noch Platzhalter.
Neben diesem Gedöns konnte ich dank zahlreicher geduldiger helfender Elfen die Vector-Klasse vervollständigen. Box2D habe ich rausgeschmissen weil es einfach nicht die Performance liefert die ich mir wünsche. Nun wird die Kollision doch selbst implementiert weil es mir sehr wichtig ist dass es schnell ist und dass auch nicht rechteckige Levels möglich sind.
Soviel für heute *gähn* ... seid gespannt was als nächstes kommt
Der Flow sieht derzeit so aus: iDS Logo (2sec oder MouseHit/Touch) -> SEDM Logo (2sec oder MouseHit/Touch) -> bnb Logo (2sec oder MouseHit/Touch) -> MainMenu -> Game <- MainMenu -> Game etc.
Das MainMenu und der Game Screen sind noch ohne Funktion, der Switch ist ein einfaches MouseHit oder ein Touch.
Die Targets Android und GLFW sorgen noch ein bisschen für Ärger. Unter Android werden die Fade timings nicht eingehalten (kann daran liegen dass die Images in FullHD vorliegen) und unter GLFW gehts garnicht (kann daran liegen dass die Images nicht in einer Grösse ^2 vorliegen. Wird noch geklärt.
Hier könnt ihr die Demo für HTML5 anschauen.
Die Grafiken sind allesamt noch Platzhalter.
Neben diesem Gedöns konnte ich dank zahlreicher geduldiger helfender Elfen die Vector-Klasse vervollständigen. Box2D habe ich rausgeschmissen weil es einfach nicht die Performance liefert die ich mir wünsche. Nun wird die Kollision doch selbst implementiert weil es mir sehr wichtig ist dass es schnell ist und dass auch nicht rechteckige Levels möglich sind.
Soviel für heute *gähn* ... seid gespannt was als nächstes kommt

Physik

Im Moment bastle ich ein wenig mit Box2D herum da mir das BoxBox Kollisionssystem nicht gefällt und ich auch mal Levels erzeugen will die nicht rechteckig sind.
Herausgekommen ist ein kleines Textprogramm welches per Mausklick Kisten erzeugt welche dann physikalisch korrekt umher fliegen.
Code: [AUSKLAPPEN]
Das kleine Testprogramm hat auch gleich die Idee mitgebracht Box2D noch ein klein wenig mehr zu wrappen um es benutzerfreundlicher zu machen.
Flash Version
Die Flash Version zeigt einen QR Code für die Android Version an.
Herausgekommen ist ein kleines Textprogramm welches per Mausklick Kisten erzeugt welche dann physikalisch korrekt umher fliegen.
Code: [AUSKLAPPEN]
Strict
Import mojo
Import box2d.common
Import box2d.collision
Import box2d.dynamics
Function RadToDeg:Int(rad:Float)
if rad <> 0.0 Then Return (rad * 180.0) / PI Else Return 0
End Function
Class CEntity
Field bodyDef:b2BodyDef
Field bodyShape:b2PolygonShape
Field body:b2Body
Field fixtureDef:b2FixtureDef
Field img:Image
Method CreateBox:Void(world:b2World, x:Float, y:Float, width:Float, height:Float, static:Bool = False)
Self.fixtureDef = New b2FixtureDef()
Self.bodyShape = New b2PolygonShape()
Self.bodyDef = New b2BodyDef()
If static = True
Self.bodyDef.type = b2Body.b2_staticBody
Else
Self.bodyDef.type = b2Body.b2_Body
endif
Self.fixtureDef.density = 1.0
Self.fixtureDef.friction = 0.3
Self.fixtureDef.restitution = 0.1
Self.fixtureDef.shape = Self.bodyShape
Self.bodyDef.position.Set(x, y)
Self.bodyShape.SetAsBox(width, height)
Self.body = world.CreateBody(Self.bodyDef)
Self.body.CreateFixture(Self.fixtureDef)
Self.bodyDef.allowSleep = True
Self.bodyDef.awake = True
End
Method CreateBox:Void(world:b2World, img:Image, x:Float, y:Float, static:Bool = False)
Self.img = img
Self.img.SetHandle(Self.img.Width() / 2, Self.img.Height() / 2)
Self.CreateBox(world, x, y, img.Width() / 2, img.Height() / 2, static)
End
Method SetFriction:Void(friction:Float = 0.5)
Self.fixtureDef.friction = friction
Self.body.CreateFixture(Self.fixtureDef)
End
Method SetDensity:Void(density:Float = 1.0)
Self.fixtureDef.density = density
Self.body.CreateFixture(Self.fixtureDef)
End
Method SetRestitution:Void(restitution:Float = 0.1)
Self.fixtureDef.restitution = restitution
Self.body.CreateFixture(Self.fixtureDef)
End
Method SetMass:Void(mass:Float)
Local md:b2MassData = new b2MassData()
Self.body.GetMassData(md)
md.mass = mass
Self.body.SetMassData(md)
End
Method SetImage:Void(img:Image)
Self.img = img
End
Method Draw:Void(ratio:Float = 1.0)
if Self.img <> Null
Local x:Float = self.body.GetPosition().x
Local y:Float = self.body.GetPosition().y
Local r:Float = RadToDeg(Self.body.GetAngle()) * -1
DrawImage(Self.img, x, y, r, 1.0, 1.0, 0)
EndIf
End
End
Class CWorld
Field world:b2World
Field m_velocityIterations:int
Field m_positionIterations:int
Field m_timeStep:Float
Field ratio:Float
Field entities:List<CEntity>
Method New(ratio:Float = 1.0, gravityX:Float = 0.0, gravityY:Float = 10.0)
Self.ratio = ratio
Self.world = New b2World(New b2Vec2(gravityX, gravityY), True)
Self.world.SetGravity(new b2Vec2(gravityX, gravityY))
Self.m_velocityIterations = 3
Self.m_positionIterations = 3
Self.m_timeStep = 1.0 / 10.0
Self.entities = New List<CEntity>()
End
Method update:Void()
Self.world.TimeStep(self.m_timeStep, self.m_velocityIterations, self.m_positionIterations)
Self.world.ClearForces()
End
Method render:Void()
if Self.entities.Count() > 0
For Local e:CEntity = eachin self.entities
if e <> Null e.Draw(self.ratio)
Next
EndIf
End
Method CreateBox:CEntity(x:Float, y:Float, width:Float, height:Float, static:Bool = false)
Local entity:CEntity = new CEntity()
entity.CreateBox(Self.world, x, y, width, height, static)
Self.entities.AddLast(entity)
Return entity
End
Method CreateBox:CEntity(img:Image, x:Float, y:Float, static:Bool = False)
Local entity:CEntity = new CEntity()
entity.CreateBox(Self.world, img, x, y, static)
Self.entities.AddLast(entity)
Return entity
End
End
Class CTest extends App
Field world:CWorld
Field player:Image
Field playerEntity:CEntity
Field groundEntity:CEntity
Field ratio:Float
Method OnCreate:Int()
Self.ratio = 640.0 / DeviceWidth()
Self.world = New CWorld(Self.ratio)
Self.player = LoadImage("player.png")
Self.groundEntity = Self.world.CreateBox(0, DeviceHeight() - 10, DeviceWidth(), 10, True)
SetUpdateRate(60)
Return 0
End
Method OnUpdate:Int()
Self.world.update()
if MouseHit() Or TouchHit()
Local mx:Float = MouseX()
Local my:Float = MouseY()
Local e:CEntity = self.world.CreateBox(self.player, mx, my)
e.body.SetAngularVelocity(Rnd(-0.1, 0.1))
EndIf
Return 0
End
Method OnRender:Int()
Cls(90, 120, 200)
#if TARGET = "android"
DrawText("touch to spwan a new crate", DeviceWidth() / 2, 20, 0.5)
#Else
DrawText("left click to spwan a new crate", DeviceWidth() / 2, 20, 0.5)
#EndIf
Self.world.render()
Return 0
End
End
Function Main:Int()
New CTest()
Return 0
End
Import mojo
Import box2d.common
Import box2d.collision
Import box2d.dynamics
Function RadToDeg:Int(rad:Float)
if rad <> 0.0 Then Return (rad * 180.0) / PI Else Return 0
End Function
Class CEntity
Field bodyDef:b2BodyDef
Field bodyShape:b2PolygonShape
Field body:b2Body
Field fixtureDef:b2FixtureDef
Field img:Image
Method CreateBox:Void(world:b2World, x:Float, y:Float, width:Float, height:Float, static:Bool = False)
Self.fixtureDef = New b2FixtureDef()
Self.bodyShape = New b2PolygonShape()
Self.bodyDef = New b2BodyDef()
If static = True
Self.bodyDef.type = b2Body.b2_staticBody
Else
Self.bodyDef.type = b2Body.b2_Body
endif
Self.fixtureDef.density = 1.0
Self.fixtureDef.friction = 0.3
Self.fixtureDef.restitution = 0.1
Self.fixtureDef.shape = Self.bodyShape
Self.bodyDef.position.Set(x, y)
Self.bodyShape.SetAsBox(width, height)
Self.body = world.CreateBody(Self.bodyDef)
Self.body.CreateFixture(Self.fixtureDef)
Self.bodyDef.allowSleep = True
Self.bodyDef.awake = True
End
Method CreateBox:Void(world:b2World, img:Image, x:Float, y:Float, static:Bool = False)
Self.img = img
Self.img.SetHandle(Self.img.Width() / 2, Self.img.Height() / 2)
Self.CreateBox(world, x, y, img.Width() / 2, img.Height() / 2, static)
End
Method SetFriction:Void(friction:Float = 0.5)
Self.fixtureDef.friction = friction
Self.body.CreateFixture(Self.fixtureDef)
End
Method SetDensity:Void(density:Float = 1.0)
Self.fixtureDef.density = density
Self.body.CreateFixture(Self.fixtureDef)
End
Method SetRestitution:Void(restitution:Float = 0.1)
Self.fixtureDef.restitution = restitution
Self.body.CreateFixture(Self.fixtureDef)
End
Method SetMass:Void(mass:Float)
Local md:b2MassData = new b2MassData()
Self.body.GetMassData(md)
md.mass = mass
Self.body.SetMassData(md)
End
Method SetImage:Void(img:Image)
Self.img = img
End
Method Draw:Void(ratio:Float = 1.0)
if Self.img <> Null
Local x:Float = self.body.GetPosition().x
Local y:Float = self.body.GetPosition().y
Local r:Float = RadToDeg(Self.body.GetAngle()) * -1
DrawImage(Self.img, x, y, r, 1.0, 1.0, 0)
EndIf
End
End
Class CWorld
Field world:b2World
Field m_velocityIterations:int
Field m_positionIterations:int
Field m_timeStep:Float
Field ratio:Float
Field entities:List<CEntity>
Method New(ratio:Float = 1.0, gravityX:Float = 0.0, gravityY:Float = 10.0)
Self.ratio = ratio
Self.world = New b2World(New b2Vec2(gravityX, gravityY), True)
Self.world.SetGravity(new b2Vec2(gravityX, gravityY))
Self.m_velocityIterations = 3
Self.m_positionIterations = 3
Self.m_timeStep = 1.0 / 10.0
Self.entities = New List<CEntity>()
End
Method update:Void()
Self.world.TimeStep(self.m_timeStep, self.m_velocityIterations, self.m_positionIterations)
Self.world.ClearForces()
End
Method render:Void()
if Self.entities.Count() > 0
For Local e:CEntity = eachin self.entities
if e <> Null e.Draw(self.ratio)
Next
EndIf
End
Method CreateBox:CEntity(x:Float, y:Float, width:Float, height:Float, static:Bool = false)
Local entity:CEntity = new CEntity()
entity.CreateBox(Self.world, x, y, width, height, static)
Self.entities.AddLast(entity)
Return entity
End
Method CreateBox:CEntity(img:Image, x:Float, y:Float, static:Bool = False)
Local entity:CEntity = new CEntity()
entity.CreateBox(Self.world, img, x, y, static)
Self.entities.AddLast(entity)
Return entity
End
End
Class CTest extends App
Field world:CWorld
Field player:Image
Field playerEntity:CEntity
Field groundEntity:CEntity
Field ratio:Float
Method OnCreate:Int()
Self.ratio = 640.0 / DeviceWidth()
Self.world = New CWorld(Self.ratio)
Self.player = LoadImage("player.png")
Self.groundEntity = Self.world.CreateBox(0, DeviceHeight() - 10, DeviceWidth(), 10, True)
SetUpdateRate(60)
Return 0
End
Method OnUpdate:Int()
Self.world.update()
if MouseHit() Or TouchHit()
Local mx:Float = MouseX()
Local my:Float = MouseY()
Local e:CEntity = self.world.CreateBox(self.player, mx, my)
e.body.SetAngularVelocity(Rnd(-0.1, 0.1))
EndIf
Return 0
End
Method OnRender:Int()
Cls(90, 120, 200)
#if TARGET = "android"
DrawText("touch to spwan a new crate", DeviceWidth() / 2, 20, 0.5)
#Else
DrawText("left click to spwan a new crate", DeviceWidth() / 2, 20, 0.5)
#EndIf
Self.world.render()
Return 0
End
End
Function Main:Int()
New CTest()
Return 0
End

Das kleine Testprogramm hat auch gleich die Idee mitgebracht Box2D noch ein klein wenig mehr zu wrappen um es benutzerfreundlicher zu machen.
Flash Version
Die Flash Version zeigt einen QR Code für die Android Version an.
let's go

vor gefühlten 100 Jahren habe ich ein Spiel namens "blox'n'balls" mit Blitz3D entwickelt. Es handelte sich dabei um einen Breakout Clone. Jetzt baue ich das Game mit Monkey neu und werde es für diverse Plattformen veröffentlichen.
Wer sich das Original Spiel anschauen möchte kann es sich gerne herunterladen (ca. 14MB), wer das ganze Paket, inklusive Entwicklerdaten, Rohdaten, Photoshop-Dateien etc. möchte kann sich das fette Paket http://www.colorflow.de/pub/download/bnb_full.zip herunterladen (ca. 117MB).
Für Vista und Windows7 User: Blitz3D hat ordentlich Macken. Öffnet bitte die Datei "blox.ini" mit dem Texteditor Eures geringsten Mißtrauens und fügt am Ende ein: "windowed" ... nur im Fenstermodus lässt sich bnb unter Vista oder Win7 problemlos spielen.
Das neue bnb wird vorrangig für Mobiles entwickelt und setzt somit auf Touchscreens. Während man auf Mouse-basierten Systemen die Maus nach links und rechts bewegt um das Paddle zu steuern, wischt man auf Mobiles das Paddle mit dem Finger nach links und rechts. Die Bwegung des Paddles im Moment der Kollision des Balls mit dem Paddle beeinflusst die neue Flugrichtung des Balls.
Alles was das originale bnb konnte wird natürlich auf die NG Version übertragen: Items welche manuell verwendet werden, Items welche automatisch beim einsammeln ausgelöst werden, der Shop um Items zu kaufen, die Online Highscore "iScore" ...
Wer schonmal einen Ball durch die Gegend schiessen will darf das unter http://www.sedm.de/monkey/bnb/html5/ tun.
Wer sich das Original Spiel anschauen möchte kann es sich gerne herunterladen (ca. 14MB), wer das ganze Paket, inklusive Entwicklerdaten, Rohdaten, Photoshop-Dateien etc. möchte kann sich das fette Paket http://www.colorflow.de/pub/download/bnb_full.zip herunterladen (ca. 117MB).
Für Vista und Windows7 User: Blitz3D hat ordentlich Macken. Öffnet bitte die Datei "blox.ini" mit dem Texteditor Eures geringsten Mißtrauens und fügt am Ende ein: "windowed" ... nur im Fenstermodus lässt sich bnb unter Vista oder Win7 problemlos spielen.
Das neue bnb wird vorrangig für Mobiles entwickelt und setzt somit auf Touchscreens. Während man auf Mouse-basierten Systemen die Maus nach links und rechts bewegt um das Paddle zu steuern, wischt man auf Mobiles das Paddle mit dem Finger nach links und rechts. Die Bwegung des Paddles im Moment der Kollision des Balls mit dem Paddle beeinflusst die neue Flugrichtung des Balls.
Alles was das originale bnb konnte wird natürlich auf die NG Version übertragen: Items welche manuell verwendet werden, Items welche automatisch beim einsammeln ausgelöst werden, der Shop um Items zu kaufen, die Online Highscore "iScore" ...
Wer schonmal einen Ball durch die Gegend schiessen will darf das unter http://www.sedm.de/monkey/bnb/html5/ tun.
