[SOLVED] Raw drawing - Doing it right?

Started by TomatoesInTheHead, Wed 06/04/2011 12:53:01

Previous topic - Next topic

TomatoesInTheHead

I've not worked with raw drawing and dynamic sprites (or really any "advanced scripting" in AGS beyond ChangeRoom and Say) so far, and tried to make a simple animated real-time clock to test it out:

Code: ags

  //set the time (for now, just use current time)
  DateTime* dt = DateTime.Now;
  int gametimeh = dt.Hour;
  int gametimem = dt.Minute;
  int gametimes = dt.Second;
  //clock position and size
  int clockposx = 100;
  int clockposy = 100;
  int clockradius = 50;
  //draw it directly onto the background
  DrawingSurface* ds = Room.GetDrawingSurfaceForBackground();
  //draw the outline of a circle
  ds.DrawingColor = 15; //white
  ds.DrawCircle(clockposx, clockposy, clockradius);
  ds.DrawingColor = 0; //black
  ds.DrawCircle(clockposx, clockposy, clockradius-1);
  ds.DrawingColor = 15; //white
  //draw hands
  ds.DrawLine(clockposx, clockposy,
    clockposx+FloatToInt(0.6*IntToFloat(clockradius)*Maths.Sin(Maths.DegreesToRadians(IntToFloat(gametimeh)*30.0)), eRoundNearest), 
    clockposy-FloatToInt(0.6*IntToFloat(clockradius)*Maths.Cos(Maths.DegreesToRadians(IntToFloat(gametimeh)*30.0)), eRoundNearest), 3);
  ds.DrawLine(clockposx, clockposy,
    clockposx+FloatToInt(0.9*IntToFloat(clockradius)*Maths.Sin(Maths.DegreesToRadians(IntToFloat(gametimem)*6.0)), eRoundNearest), 
    clockposy-FloatToInt(0.9*IntToFloat(clockradius)*Maths.Cos(Maths.DegreesToRadians(IntToFloat(gametimem)*6.0)), eRoundNearest), 2);
  ds.DrawLine(clockposx, clockposy,
    clockposx+FloatToInt(0.9*IntToFloat(clockradius)*Maths.Sin(Maths.DegreesToRadians(IntToFloat(gametimes)*6.0)), eRoundNearest), 
    clockposy-FloatToInt(0.9*IntToFloat(clockradius)*Maths.Cos(Maths.DegreesToRadians(IntToFloat(gametimes)*6.0)), eRoundNearest), 1);  
  ds.Release();

This code is in the rep_ex_always function of a room.

Now, before you try to figure out what I did wrong: This works fine. ;)
It draws a clock with the current time on the background, and since the clock stays at the same position, it's not a problem that the original background is drawn over.

My main question is: Is this the right approach in terms of efficiency, performance, memory usage, and code style, or did I miss something? Would it be better or worse (unless necessary) to draw on an object/DynamicSprite than on the background?

And if I have to use a DynamicSprite, do I always have to store it in a global variable? So I can't just write something like
Code: ags
DynamicSprite* ds = DynamicSprite.Create(101,101);
DrawingSurface sf = ds.GetDrawingSurface();
//...draw clock on surface like before...
sf.Release();
oClock.Graphic = ds.Graphic;
ds.Delete(); //or not?
//end of function

in my rep_ex_always function, since the sprite would be deleted right after I assigned it (by the Delete function or by the AGS memory manager) and the graphic of oClock would fall back to and display the default sprite?

My other, general questions are:
a) Is it correct that I always have to use variables to store e.g. the datetime in order to access it? So there is no way to write something like "int hour = DateTime.Now.Hour;" like in similar programming languages, but I must write "DateTime* dt = DateTime.Now; int hour = dt.Hour;"?
b) Is it correct that there's no way to omit all these IntToFloats and FloatToInts other than storing each value in a float and int value in parallel (so, I would define "int clockradius = 50; float clockradius_f = 100.0;" for example)?
c) Is it correct that there is no other way to draw the outline of a circle than how I did it?

For b) and c) I found some older threads confirming this, more or less, but I want to make sure it hasn't changed since then.

Thanks for any confirmations and suggestions! :)

Khris

Main question: pretty much yes. I'd indeed draw it to a DynamicSprite or at least include offset coordinates to be able to reposition the clock, but in terms of "efficiency, performance, memory usage, and code style" it's fine.

Yes, you need a global sprite (or more precisely: a non-"local to the function" sprite), otherwise you end up with sprite 0.
Declaring the sprite just above the function will suffice.

a) Afaik, yes. The reason I take it is that using .Now queries the system for the time/date once, storing all values in the members of the DateTime object, creating a consistent dataset of one precise moment.
While a few loops between queries to e.g. DayTime.Hour and DayTime.Minute usually don't skew the result, you could in theory end up with 7:00 when it is in fact 8:00 (because the clock just switched from 7:59 (hour = 7) to 8:00 (minute = 0)).

b) What I do is calculate everything using floats, then turn them into ints for drawing (that way, rounding errors don't accumulate). While storing each value twice is nonsensical on its own, I don't see how you could avoid FloatToInts and IntToFloats by doing it.

c) You could go from 0° to 360°, drawing lines from each cos(), sin() to the next. If you vary the angle step depending on the radius, the line will probably be as clean as you can get. Also you don't have to draw the outline first, you can draw it on top of other things using this method.

TomatoesInTheHead

Good, thanks for the clarfications :)

Quote from: Khris on Wed 06/04/2011 14:37:52
Yes, you need a global sprite (or more precisely: a non-"local to the function" sprite), otherwise you end up with sprite 0.
Declaring the sprite just above the function will suffice.
Ah yeah, defining it just for the room makes more sense in this case.

Quotea) Afaik, yes. The reason I take it is that using .Now queries the system for the time/date once, storing all values in the members of the DateTime object, creating a consistent dataset of one precise moment.
While a few loops between queries to e.g. DayTime.Hour and DayTime.Minute usually don't skew the result, you could in theory end up with 7:00 when it is in fact 8:00 (because the clock just switched from 7:59 (hour = 7) to 8:00 (minute = 0)).
Though this wouldn't apply in other cases, like "mySprite.GetDrawingSurface().DrawCircle(...)" or "Character.GetAtScreenXY(...).SetAsPlayer()", which you also can't write.
But I see, none of these uses would actually make sense as a one-liner, the DrawingSurface in the first example must be released and hence be stored in a variable, and the character in the second example must be checked for null anyways... so yeah, it's probably to prevent people from running into problems with this in the first place.


Using sin/cos would be an alternative to draw a circle outline, yes. I just hoped there would be an undocumented function to set the border color separately or something. Maybe in the next release... (along with functions to draw polygons, ellipses and bezier curves... although I'm not sure I'd need them too often)

Khris

QuoteCharacter.GetAtScreenXY(...).SetAsPlayer()

Ah, I see.

This used to irk me, too, but as you said, this would often result in null pointer errors.
You can use multiple dots in other circumstances though.

An example would be InvWindow.CharacterToUse.Name.
Another would be what I use in one project, with f being a struct instance and weapon an inventory item member: f[1].weapon.GetProperty("attackloop")

monkey0506

IIRC CJ has said that regarding DateTime.Now.Hour (and the like) that this is actually a limitation in the editor/compiler, but that the run-time engine itself would have no problem with it. The limitation specifically involves accessing a non-static property (like Hour) of a static property (like Now). If you were attempting to access a static property of a static property (which..I don't think any such properties exist..built-in) then it wouldn't have an issue.

The programming conventions like Character.GetAtScreenXY().SetAsPlayer() actually are a separate issue, though also probably related to the compiler moreso than the engine. I would think that prior to implementing something like that, it would probably be worthwhile to implement some level of error handling.

SMF spam blocked by CleanTalk