Abu Framework

Monkey Programming Forums/User Modules/Abu Framework

Belimoth(Posted 2012) [#1]
Abu is the result of my experience with other game development frameworks, primarily flash and javascript-based ones.
It is 2D only, with a lean towards retro-style action games.

Abu is a work in progress; I am cleaning up and consolidating my somewhat scattered code base so that it is useful and easy to implement.


Belimoth(Posted 2012) [#2]
Download: Abu 0.01 Most recent link in first post.
This first incarnation contains only three parts of the eventual whole, but the main file 'abu.monkey' shows what is to come.
Also included is a demo of the animation submodule; if you do not have Diddy then simply comment out 'Import diddy' and it will still work.

I. abucolor
[monkeycode]
'To use abucolor, add these two lines to your header:
Alias SetColor = abucolor.SetColor
Alias Cls = abucolor.Cls

'This allows you to call mojo's SetColor() and Cls() functions using colors
'in the hex format $rrggbb, in addition to the standard usage.
Cls($000000)
SetColor($00FF00)

'You can also use the Color class to define your own colors in hex:
Global myColor := New Color($112233)

'...or access the individual components of a hex color easily:
Print Color($FF0000).r

'This class is not intended to be used for pixel-level manipulations of images.
[/monkeycode]

II. abuimage
[monkeycode]
'this is a simple class that adds a little functionality to mojo's Image.

'load a single image like this:
Global myImage := New AbuImage("myImage.png")

'or load an animation sheet like this:
Global mySheet := New AnimationSheet("mySheet.png", 16, 16)

'these constructors can be called anywhere in your code,
'however the LoadImages() function must be called before drawing anything.

'the AbuImage class has the following significant methods:
SetHandle:Void(x:Int, y:Int)
Draw:Void(x:Int, y:Int, rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1)
DrawFlipped:Void(x:Int, y:Int, flip:Int = FLIP_X, rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1)

'the flip parameter can be FLIP_X, FLIP_Y, or FLIP_X|FLIP_Y
'flipping is applied before rotation and preserves the handle of the image

'the AnimationSheet class has these additional significant methods:
DrawFrame:Void(x:Int, y:Int, frame:Int, rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1)
DrawFrameFlipped:Void(x:Int, y:Int, frame:Int, flip:Int = FLIP_X, rotation:Float = 0, sX:Float = 1, sY:Float = 1)

'these function the same as AbuImage.Draw() and DrawFlipped(), but take a frame parameter.
[/monkeycode]

III. abuanimation
[monkeycode]
'this is the largest submodule, the star of which is the AbuSprite class.

'here is partial documentation for that class:
Property animationSheet:AnimationSheet '[read-only]
Property currentAnimation:String '[read-only], name of current animation
Property nextAnimation:String '[read-only], name of qeue'd animation
Property loopCount:Int '[read-only], number of times the current animation has looped
Method SetHandle:Void(x:Int, y:Int)
Method SetAnimationSheet:Void(animationSheet:AnimationSheet)
Method SetAnimationSheet:Void(path:String, frameWidth:Int, frameHeight:Int)
Method AddAnimation:Void(name:String, sequence:Int[], frameDuration:Int, loop:Bool = True)
Method AddAnimation:Void(name:String, subAnimations:String[])
Method PlayAnimation:Void(name:String, reset:Bool = True, rate:Float = 1)
Method QeueAnimation:Void(name:String, reset:Bool = True, rate:Float = 1)
Method OnAnimationLoop:Void(animation:String)
Method OnAnimationEnd:Void(animation:String)
Method AddOffset:Void(name:String, offsetX:Int[], offsetY:Int[], cumulative:Bool = False)
Method AddDisplacement:Void(name:String, displaceX:Int[], displaceY:Int[])
Method SetRate:Void(rate:Float)
Method Draw:Void(x:Int, y:Int, rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1)

'animationdemo.monkey is an example of how to use this class.
'the AddOffset() and AddDisplacement() methods are not yet implemented.
[/monkeycode]


Belimoth(Posted 2012) [#3]


In animationdemo.monkey I have imported Diddy to show that there are no indentifier conflictions so far between my code and Diddy. I love using Diddy myself and have no intention of reinventing all of the simple utilities that can be found in it, or any other publicly available module for that matter.

I also intend to make Abu as "buffet-style" as possible; low inter-dependency between submodules so that they can be used individually.


Belimoth(Posted 2012) [#4]
Loading from texture atlases now works and I've borrowed the css from the monkey docs to make nice looking documentation (still a work-in-progress).


Belimoth(Posted 2012) [#5]
Added abutime & abuinput submodules and a lot of documentation.
Looking for assets I can distribute with the bananas.


Halfdan(Posted 2012) [#6]
Nice! I like how simple it is. How are you planning to implement abureplay?


Belimoth(Posted 2012) [#7]
Thank you :)

You can see I've put abureplay next to abuinput and aburandom, basically the plan is to store whatever input the user gives and any random numbers generated on a timeline in record mode, and then those modules will read from the timeline instead of functioning normally during playback mode.

I'd like to make this easy to use with physics libraries such as nape, box2d, and chipmunk so I'm researching the best ways to make those deterministic.

In the meantime I've got a very simple "physics" module for collisions and such that will be deterministic for sure. It's tied into the whole entity system which is what I'm finishing up now.


Halfdan(Posted 2012) [#8]
Sounds promising, especially functions like Sprites, Timers and Colors are very useful.

I noticed some traces of Image variations in AbuImage, are you still planning on implementing such a feature? I've been wanting to do palette shifting a while.

Also the repo is acting strange, when I "hg clone https://code.google.com/p/abu-framework/" I don't get the most recent version (visible when browsing on google code), I get the same version as the .zip download.

Thanks!


Belimoth(Posted 2012) [#9]
EDIT: Yeah I see that the Timer files are missing.


Belimoth(Posted 2012) [#10]
Trying to fix cloning issues, but I just added entitytest.monkey that previews the incomplete entity system.

I am still pursuing palette-swapping capability but it involves target-specific considerations, I think.
Trying to decide on the best way to standardize the palette information that you feed it, without requiring png8's or something.

EDIT: I believe the cloning works now, also there's a more recent zip. Requires diddy for its xml module.


Halfdan(Posted 2012) [#11]
I also had issues with palette-shifting; I ended per-pixel redrawing the image, which was too ugly. If you know a solution I could look into it.

Wouldn't png palette swapping be hard also, needing target specific code?

I'll check the new code tomorrow; I just adapted my project to use Abu for all sprites :)


Belimoth(Posted 2012) [#12]
Cool :) I'd like to see what you make with it.

I'm still looking into image variations in general. That includes palette shifting and also tinting since doing it by way of SetColor is so slow on the HTML5 target. I've got a half-baked idea for a system that helps with smart video memory usage that is tied in with this (That's the AbuTextureAtlas fragments you can see lying around).

If I can get that to work in an elegant way it would allow for some neat effects like 'real-time' tinting for HTML5 and temporal aliasing for animations that are animating faster than the given framerate.

We'll see if I can manage it. I don't want this project to get leaky.


Belimoth(Posted 2012) [#13]
Just realized that not having a DrawFlipped() method at the Sprite level is a flaw. I have been testing things with one animation each for left and right-facing things but that's obviously not ideal. The capability is there at the AbuImage level, I just need to extend it up the hierarchy.

EDIT: On further contemplation, including it would add some complexity to the displacement aspect of sprites when they are an entity component. How does it know which direction to displace the entity? I'd like to think of a solution that doesn't require that I add a mandatory orientation component to the Entity class.

One solution could be to add a flip:Int parameter to the PlayAnimation() method, but this doesn't allow for changing direction in the middle of an animation without some maintenance code.

I've also been thinking about how/if to allow sprites to take their animation frames from multiple images. I've been assembling simple games to test all the framework's aspects and one of those involved a sprite atlas that had been constructed from many related images, one for each of the protagonist's animations. Counting the frames to see where a specific animation started was tedious, I'd like to be able to do this in a more intelligent way.


Belimoth(Posted 2012) [#14]
Just finished the first draft of abutween. I am very happy with it :)
It uses reflection to tween properties via Invoke(). It can only tween Float properties at the moment, and it only does linear interpolation but it works!
This is the first time I've used the reflection module for anything, I'm very pleased with how everything turned out.

When it is finalized it will be integrated with the entity system, which is still a work in progress.
In the meantime, here is the code if anyone wants to take it for a spin:
format_codebox('
'abutween.monkey by Belimoth
Strict
Import abutime
Import abujuggler
Import reflection

Const TWEEN_DEFAULT:Int = 0

Interface ITweener
Method AddTween:Void(name:String, value:Float, duration:Int, flags:Int = TWEEN_DEFAULT)
Method PauseTween:Void(name:String)
Method ResumeTween:Void(name:String)
Method RemoveTween:Void(name:String)
End

Class Tweener Implements ITweener

Public
Method AddTween:Void(name:String, value:Float, duration:Int, flags:Int = TWEEN_DEFAULT)
Local tweenNew := New AbuTween(Self, name, value, duration, flags)
_tweens.Set(name, tweenNew)
End

Method PauseTween:Void(name:String)

End

Method ResumeTween:Void(name:String)

End

Method RemoveTween:Void(name:String)

End

Private
Field _tweens := New StringMap<AbuTween>

End


Function UpdateTweens:Void(timePassed:Int = -1)
If timePassed = -1 Then timePassed = Tick()
AbuTween.juggler.Juggle(timePassed)
End

Class AbuTween Implements IJuggleable
'Global ARGUMENT:ClassInfo[] = [ FloatClass() ]
Global juggler := New Juggler<AbuTween>

Field _active:Bool = True

Field tweener:ITweener

Field targetObject:Object
Field targetGetProperty:MethodInfo
Field targetSetProperty:MethodInfo
Field valueCurrentBoxed:Object[]
Field valueStart:Float, valueEnd:Float
Field timeStart:Int, timeEnd:Int
Field timePassed:Int
Field duration:Int
Field completion:float

Method New(targetObject:Object, name:String, value:Float, duration:Int, flags:Int = TWEEN_DEFAULT)
Self.targetObject = targetObject
Local classInfo := GetClass(targetObject)
If classInfo = Null Then Error("Reflected class not found.")
Self.targetGetProperty = classInfo.GetMethod(name, [], True)
If targetGetProperty = Null Then Error("Reflected get method '" + name +"' not found.")
Self.targetSetProperty = classInfo.GetMethod(name, [ FloatClass() ], True)
If targetSetProperty = Null Then Error("Reflected set method '" + name +"' not found.")
Self.valueCurrentBoxed = New Object[1]
_GetValue()
Self.valueStart = FloatObject(valueCurrentBoxed[0]).value
Self.valueEnd = value
Self.timeStart = CurrentTime()
Self.duration = duration
Self.timeEnd = timeStart + duration
juggler.Add(Self)
End

Method Destroy:Void()
juggler.Remove(Self) 'TODO this doesn't remove the tween from the tweener
End

Method Active:Bool() Property
Return _active
End

Method Update:Void(timePassed:Int)
Self.timePassed += timePassed
Self.completion = Float(Self.timePassed) / Float(duration)
Local valueCurrent:Float
If completion >= 1
valueCurrent = valueEnd
Else
valueCurrent = valueStart * (1 - completion) + valueEnd * completion
Endif
FloatObject(valueCurrentBoxed[0]).value = valueCurrent
_SetValue()
If completion >= 1 Then Destroy()
End

Method _GetValue:Void()
valueCurrentBoxed[0] = targetGetProperty.Invoke(targetObject, [])
End

Method _SetValue:Void()
targetSetProperty.Invoke(targetObject, valueCurrentBoxed)
End
End
')


And here is an example file:
format_codebox('
'tweentest.monkey by Belimoth
Strict
#REFLECTION_FILTER="abutween|tweentest*"
Import abutween
Import reflection

Class Thing Extends Tweener
Method X:Float() Property Return _x End
Method X:Void(xNew:Float) Property _x = xNew End

Method Y:Float() Property Return _y End
Method Y:Void(yNew:Float) Property _y = yNew End

Method New(x:Int, y:Int)
Self._x = x
Self._y = y
End

Method Render:Void()
DrawOval _x - 7, _y - 7, 15, 15
End

Private
Field _x:Int
Field _y:Int
End

Class MyApp Extends App
Field thing:Thing

Method OnCreate:Int()
SetUpdateRate(60)
thing = New Thing(50, 240)
Return 0
End

Method OnUpdate:Int()
UpdateTime()
UpdateTweens()
If MouseHit(MOUSE_LEFT)
thing.AddTween("X", MouseX(), 1000)
thing.AddTween("Y", MouseY(), 1000)
Endif
Return 0
End

Method OnRender:Int()
Cls()
SetColor(255, 255, 255)
thing.Render()
Return 0
End
End

Function Main:Int()
New MyApp()
Return 0
End
')
I haven't downloaded the newest version of Monkey, I think maybe there have been some changes to #REFLECTION_FILTER? Adjust if necessary.

It's as easy as:
[monkeycode]
#REFLECTION_FILTER="whatever"
Import abutime
Import abujuggler
Import abutween
Import reflection
...
Class MyObject Extends Tweener
Method X:Float() Property
Method X:Void(xNew:Float) Property
...
myObject.AddTween("X", xDestination, tweenDuration)
...
UpdateTime()
UpdateTweens()
[/monkeycode]

The only restrictions are that the property you are tweening must have identically named get and set methods, and you must include the relevant class in the reflection filter.


Belimoth(Posted 2012) [#15]
Here is what the example looks like. Click to tween the ball's position :)


ordigdug(Posted 2012) [#16]
Cool :)


Belimoth(Posted 2012) [#17]
Whew, big update today! I am exhausted.
The repository is fresh and I'll have a new zip up in mere moments.

Minor changes:
- abutween.monkey has been renamed abuschedule.monkey and has added support for named Timer management.
- abujuggler.monkey has been merged into abutime.monkey
- The modified SetColor and Cls commands have been moved into a new submodule called 'mojomodifications'. This isn't imported by the main abu module.
- abucolorconstants.monkey has been merged into abucolor.monkey
- AbuTimer.Unpause() renamed to Resume().

Major changes:
- Support for Animations outside of an AbuSprite object has been removed. Stand-alone animations didn't add any usefulness and were harder to use.
- All property methods renamed in 'PascalCase'.
- The scheduling module works for tweening both Float and Int properties without having to specify.
- The AddAnimation function now takes an optional start parameter, similar to the first_cell parameter of LoadAnimImage from BlitzMax.
- The AddAnimation function can now accept an array of values for the frameDuration parameter.
- Much more documentation.

Tentative changes:
- What used to be the Entity class is now the EntityBuilder<> class.
- The AbuEntity class is now a prebuilt EntityBuilder<> with default components.

Incomplete Features:
- The Physics component of Entities isn't done yet.
- Tweening only uses linear interpolation, and there are no easing options yet.
- No modules add themselves to the #REFLECTION_FILTER. If you want to use the tweening features the easiest way is to just add "abu*" to the filter.

What's Next:
- Finishing all of the above incomplete features.
- The abucamera, abuactor, and abustage submodule block. These are all interrelated.
- Bananas! I've got a few good demos already, but I'd like to make more before I add them to the repository.
- Finishing the abudebug module. Really I'm just deciding on the best API for this.

Merry Christmas, and Happy Holidays :)


Belimoth(Posted 2013) [#18]
New update!

Minor Changes
EntityComponent.Tie renamed to Initialize
juggler.Juggle and IJuggleable.Update switched
Color moved to mojomodifications and made private
SetColor(rgb:Int[]) and Cls(rgb:Int[]) added to mojomodifications
abuimagebank merged into abuimage and all classes made private
ISprite.QeueAnimation renamed to QueueAnimation
jugglers exposed for AbuSprite, VelocityComponent, and ScheduleComponent

Major Changes
abuanimation restructured, Animation class made private. standalone animations now fully deprecated
basic physics now mostly functional
abutilemap and abucollisionmap added, these are unfinished

Be aware, for identifier conflict reasons, that there is an AABB class floating around in there somewhere. It isn't documented because it hasn't found a submodule to call home yet.


Belimoth(Posted 2013) [#19]
Cameras are coming along nicely: demo


Belimoth(Posted 2013) [#20]
Okay! Cameras are finished now, except for a few small details I'd like to add. Here is a demo showing what they are capable of.
Also new is virtual resolution capabilities, which the cameras adjust to, and a shift towards more thorough encapsulation in the form of a state-based framework.

For the curious, here is the demo source:
format_codebox('
'cameratest.monkey by Belimoth

Strict
Import mojo
Import abu



Class Background Extends AbuEntity
Field image := New AbuImage("landscape.png")

Method New()
InitializeComponents()
End

Method Render:Void()
image.Draw(0, 0)
End
End



Class MyApp Extends AbuApp
Method OnCreate:Int()
Super.OnCreate()
SetVirtualDisplay(320, 200)
SetState( New GameState() )
Return 0
End
End



Class GameState Extends AbuState
Field background:Background
Field camera:AbuCamera

Method OnStart:Void()
background = New Background()
Abu.stage.Add(background)
camera = New AbuCamera(10, 10, 64, 64)
Abu.cameras.AddLast(camera)
Print "virtual resolution: 320 x 200"
Print "use QAZ to change zoom"
End

Method OnUpdate:Void()
Abu.Update()
camera.xTarget = VirtualMouseX()
camera.yTarget = VirtualMouseY()
If KeyHit(KEY_Q) Then camera.zoom = 2.0
If KeyHit(KEY_A) Then camera.zoom = 1.0
If KeyHit(KEY_Z) Then camera.zoom = 0.5
End

Method OnRender:Void()
Abu.Render()
End
End



Function Main:Int()
New MyApp()
Return 0
End
')

I've updated the repository.


Belimoth(Posted 2013) [#21]
I've experimented with a lot of different options for tween equations, and decided to go with Skn3's code from the bananas folder.
Integrating it took about 30 seconds, I really shouldn't have taken this long to make that decision.


Anyhow, see the difference here.


Belimoth(Posted 2013) [#22]
I've uploaded a zip containing all the new stuff.

Minor Changes
Position.AngleTo() output adjusted to match coordinate system (0-360)
MapX(), MapY(), & At() added to abutilemap; MapX() and MapY() removed from abucollisionmap
bug with collision map bumpers fixed
abucolor removed, color constants moved into mojomodifications
IRenderable and AbuRenderObject added, these may be temporary
switched to skn3.xml for atlas loading

Major Changes
abuframework added
abuprofiler added
tweening equations usable via Skn3's tween code
tweens can now be applied to fields


Snader(Posted 2013) [#23]
Nice work Belimoth!


Belimoth(Posted 2013) [#24]
Thank you very much! I appreciate the affirmation.

I've been experimenting with dithering velocities and have started to converge on a solution that resembles the AddDisplacement aspect of sprites.

I've also got a working prototype of real-time tinting in HTML5, but I don't like the idea of having native code to maintain. Maybe as the dust settles on the new versions of Monkey I won't be so apprehensive. In order to do it right I've also got some figuring to do as far as video memory management.

I am excited by the possibilities!


Belimoth(Posted 2013) [#25]
Doing another pass on sprites, the way I handled handles doesn't really make sense in the big picture.
The DrawFlipped methods won't do much now which makes me weary but I guess that's the cost of trying new things.


Belimoth(Posted 2013) [#26]
Silly typos. The abuvelocitycomponent.VelocityComponent methods should look like this:

format_code('
Method XDisplacement:Int() Property
Local xTemp:Float = _xVelocity + _xRemainder
Local dx:Int = Int(xTemp)

_xRemainder = xTemp - dx

Return dx
End

Method YDisplacement:Int() Property
Local yTemp:Float = _yVelocity + _yRemainder
Local dy:Int = Int(yTemp)

_yRemainder = yTemp - dy

Return dy
End
')
It works how it is but movement is very jagged.