Day-Night Cycle, Seasons, Random Events, Showing Variables on-screen

Started by RenatoDias, Mon 30/12/2013 01:09:25

Previous topic - Next topic

RenatoDias

Hey everyone. I've come here because I have a few questions, which, by fiddling with the engine I couldn't get a way to do.
First: Is there a way to do Day-Night cycle, with the time going on second by second(1 real second=1 virtual minute) and showing it on a GUI, along with Day number?

Second: With the Day-Night Cycle, I want to do a season-based game(does anyone remember Harvest Moon?) with seasons like Spring/Summer/Fall/Winter.

Third: Along with Seasons, I would also like to have Random Events, such as Rain, extremely cold days and nights, Snowstorms, Heatwaves(extremely hot days).

Thanks, and excuse me if this is basic knowledge.

Snarky

OK, lots of questions there...

First: Yes, all these things are definitely doable in AGS. Some of it sounds like significant work, though. Not necessarily difficult to code, just lots of work to set everything up. Second: I'll mention a bunch of AGS methods and properties. These are all documented in the manual, so look them up. Anyway...

To display something like the day and time, make a GUI with a GUI label, and use the Label.Text property and String.Format() to display the string you want. You probably want to update the content of the label in repeatedly_execute().

To keep track of game time that is linked to real-world time, I'd probably use two separate counters (for game days and game minutes), updated in repeatedly_execute_always(), calculated from DateTime.RawTime. This is more reliable than counting game cycles, but you'll need a little bit of special code to handle game saving/loading or any situation where you want to pause the game, so that only time the game is actually running is counted. (But you can add that later.) If you don't care so much about staying in sync with real-world time, the easiest thing is to just increment a counter each game cycle, in repeatedly_execute_always().

As for day/night cycle, seasons, or weather, it all depends on exactly how you want these changes to manifest. It's easy enough to use the day and minute counters to calculate the season and whether it's day or night. And to make weather, you probably just pick at random from a list of options each day, right? Nothing particularly difficult about that, either. For example:

Code: AGS

enum Season {
  Spring,
  Summer,
  Fall,
  Winter
};

int currentDay;
int currentMinute;
Season currentSeason;
bool currentIsNight;

function repeatedly_execute()
{
  // update currentMinute and currentDay here

  currentIsNight = (currentMinutes< 60*6 || currentMinutes >=60*18)  // It's night from 6PM to 6AM, 18:00-06:00

  int dayOfYear = currentDay - (currentDay/364)*364;  // Assume 364 days a year for easy division into 4 seasons of 91 days
  if(dayOfYear<45)
    currentSeason = Winter;
  else if(dayOfYear<45+91)
    currentSeason = Spring;
  else if(dayOfYear<45+182)
    currentSeason = Summer;
   else if(dayOfYear<45+273)
    currentSeason = Fall;
   else
    currentSeason = Winter;
}


But once you know those things, what do you want to do with it?

Presumably you may want to display different versions of each screen? Well, then you need to draw different versions of the background, and pick which one to display (probably each time you enter the room) depending on season/day-or-night/weather. Exactly how to best do this will differ depending on your game: if different versions of the screen have very different layouts, hotspots or events, it may be best to create them as separate rooms. If the difference is mainly visual, you can just switch out the graphics, either using Room.SetBackgroundFrame (if you only have a few different versions), or using Room.GetDrawingSurfaceForBackground() and DrawingSurface.DrawImage(). Of course, you can also overlay objects and turn on special effects such as snow or rain. To make a "night" screen that is simply a darkened version of the day screen, you can create a black or dark blue GUI that covers the full screen and simply turn it on at partial transparency.

RenatoDias

Well, as I planned, a virtual year would be 120 game days(like Harvest Moon)(30 game days per season[month]).
The use of all this would be a kind of hotel-management game, and to do so, I planned to use the Harvest Moon/Grand Theft Auto time method(1 real minute=1 virtual hour), so a virtual day(24 virtual hours) would be 24 real minutes.
The method for Night would just be a darker background(image).
I plan to use a four background method(morning, day, dusk(almost night) and night).
Asked about seasons because I would need to reflect the background changes for each season, but each room can only have 5 backgrounds. Now I've been thinking and I have a method for season backgrounds.
If you could explain about working with game cycles. (Example: 60 game cycles=one real second=one game minute) And how I can make the cycle start only when the game has really started(in the first playable room).
Thanks again.

Snarky

Quote from: RenatoDias on Mon 30/12/2013 03:59:54
Well, as I planned, a virtual year would be 120 game days(like Harvest Moon)(30 game days per season[month]).

OK, doesn't really matter, the principle is the same.

QuoteThe method for Night would just be a darker background(image).

Then I'd use the GUI overlay to darken, as mentioned.

QuoteI plan to use a four background method(morning, day, dusk(almost night) and night).
Asked about seasons because I would need to reflect the background changes for each season, but each room can only have 5 backgrounds. Now I've been thinking and I have a method for season backgrounds.

Well, if you use the GUI to darken during night (and to darken a little bit during dawn and dusk), you can use four of those backgrounds to do the different seasons. As already mentioned, the alternative (if you wanted more than that) is to simply repaint the background yourself using DrawImage(). (You could also have a plain background that you add seasonal touches to in the form of objects, but that's probably fiddlier and more work.) Lots of ways to do it.

QuoteIf you could explain about working with game cycles. (Example: 60 game cycles=one real second=one game minute) And how I can make the cycle start only when the game has really started(in the first playable room).

Well, one of the advantages of working with DateTime is that you don't have to worry about game cycles, you can just count seconds. The other is that computer loops usually don't take exactly as much time as advertised, so over time you might get clock drift. (So even if the game progresses by a game minute approximately once per second, it might take noticeably more or less than a minute for it to progress by an hour, and maybe somewhere between 22 and 26 minutes to progress by a day... While DateTime on the other hand stays synced with actual time. Though I don't know how serious the problem would be in practice, and perhaps you don't care too much about accuracy anyway.)

If you really want to do it by counting cycles, simply put:

Code: AGS

// In GlobalScript
int gameCounter;
int gameSpeed;
bool runGameTime;

int currentMinute;
int currentDay;

game_start()
{
  // ... other stuff
  runGameTime = GetGameSpeed();
}

function repeatedly_execute_always()
{
  // ... other stuff

  // If game time is running, update our game counter each cycle
  if(runGameTime)
    gameCounter++;

  // Once every gameSpeed cycles, progress the game time
  if(gameCounter >= gameSpeed)
  {
    gameCounter = 0;
    currentMinute++;

    // Progress the day if we've reached midnight
    if(currentMinute >= 1440) // 60*24
    {
      currentMinute = 0;
      currentDay++;
    }
  }
}

// In your first playable room
function room_FirstLoad()
{
  // ... other stuff
  runGameTime = true;  // Start game time when you get to the first playable room
}


To organize things a little bit more cleanly, I'd probably make a script module with struct to keep track of game time and all associated data (whether it's dawn, day, dusk or night, the season, and the weather), but that's not strictly necessary.

RenatoDias

I guess the DateTime function would work better then, since cycles aren't always correct. And also, I would have to fix the gamespeed to 60 FPS and remove the speed modifier(I know how to do that), because AGS runs at 40 FPS(40 cycles per second). As I could get, the DateTime function isn't affected by the gamespeed, is it?

I'm asking this so I could get the time system done and do a intensive test to see if it would work for me before I insert the characters. It would be hard to know after the game is almost complete that the time system may not be working the way I would like it to.

So, is the DateTime function affected by how the gamespeed is adjusted or it relies solely on the PC's RealTime Clock?
Thank you.

Snarky

Quote from: RenatoDias on Mon 30/12/2013 16:35:43
I guess the DateTime function would work better then, since cycles aren't always correct. And also, I would have to fix the gamespeed to 60 FPS and remove the speed modifier(I know how to do that), because AGS runs at 40 FPS(40 cycles per second). As I could get, the DateTime function isn't affected by the gamespeed, is it?

DateTime is more accurate (and yes, is directly linked to the realtime clock), but also more complicated. I'm going back on my initial advice here, but given the questions you've been asking, maybe you should just stick to with counting the cycles? Does it really matter if it runs a little bit faster or slower than you expect? (Like I said, I haven't measured the amount of clock drift in AGS. I know that in e.g. Java it is considerable, but for AGS it might be relatively insignificant on most machines.)

You don't need to mess with the game speed either; it doesn't matter whether it's 40 or 60 or whatever. If you look at the code, you'll see that it's independent of the game speed â€" or to be more accurate, it takes it into account in the calculations. You could even change it mid-game, as long as you also update the gameSpeed variable at the same time.

The basic logic for a day-night/season system isn't terribly complicated, and the examples I've posted probably provide 50% of what you'd need to write. Most of the work comes in providing data for and dealing with all the different cases everywhere it matters (making four different versions of every background and linking them up correctly, etc.).

Khris

I think it is much easier and better to only rely on the game's frame count, since a) you can allow the player to adjust the game speed that way and b) it'll be perfectly synced to the game itself, meaning that a certain animation will always take the same amount of game time, since it is linked to the frames.

RenatoDias

Snarky, I just really need to get the logic(script) correct, because having it done, I can use it as a base and then insert the graphics and such.
Now, how would I do about showing the variables on the screen?
For example, in the demo game, the score variable is put as @SCORETEXT@ on a gui label, can I do something like with day, hour, minute and season? How would I do that?
I couldn't find anything about that on the manual. Thanks.

miguel

Renato,
from Snarky:
QuoteTo display something like the day and time, make a GUI with a GUI label, and use the Label.Text property and String.Format() to display the string you want. You probably want to update the content of the label in repeatedly_execute().
Working on a RON game!!!!!

RenatoDias

No luck in making labels work.
Snarky: If I put the room_FirstLoad function in the script, it doesn't compile.

Snarky

Quote from: RenatoDias on Mon 30/12/2013 20:59:17
No luck in making labels work.

Not a useful problem description.

QuoteSnarky: If I put the room_FirstLoad function in the script, it doesn't compile.

Yeah, there's a tricky little gotcha there, guaranteed to trip up newbies. (It doesn't even seem like a necessary thing, but it's just the way AGS is built.)

room_FirstLoad is a built-in game event. You have to go to the Edit Room tab (where your room background is), and look in the event pane (press the little lightning bolt button). Find and select the "First time enters room" event, and click the little "..." thing that appears. That will create a script hook for the event, and you can stick the relevant bit of code in there.

If you haven't read the manual and gone through the tutorial, you should definitely do that as well.

Edit: Maybe I should explain that event thing a little more: Most of the time, you can edit the AGS scripts and add new functions just by typing, but now and then... you can't. This specifically applies to creating "event handlers". An event handler is a function (also called "method") that you don't just call yourself, but which AGS calls automatically when something in particular (an "event") happens. For example, when you click on a particular object, or when you enter a room, etc. To create these event handlers, you have to go through the event pane. Just to make it even more confusing, there are some functions that are essentially just like event handlers, but which you can simply type in (repeatedly_execute_always(), for example). No, it doesn't really make sense (though there are underlying reasons for it); you just have to learn that that's how you do it.

RenatoDias

It doesn't compile even if I use the function provided by the event
It gives: "Undefined Token 'runGameTime'"

Also, how do I use the function label.text?
Tried putting it alone, but no dice.
Is there some readable material about it? AGS manual isn't explaining enough about it.

Snarky

Ah, that's because you need to either export runGameTime from the global script, or define it as a Global Variable.

For the label, it should be something like:

Code: AGS

int currentHour = currentMinute/60;
int currentMinOnly = currentMinute - currentHour*60;
lblTimeDisplay.Text = String.Format("Day %d, %d:%02d", currentDay, currentHour, currentMinOnly); // assuming your label is called lblTimeDisplay

RenatoDias

Well, it worked, but minutes seem a bit too fast. How can I make so 40/60 cycles equal to one game minute?
Thanks again.

Snarky

If you take a look at the code I posted above, here (line 12 and 21-27 in particular), and make sure you understand what it's doing, you should have your answer.

RenatoDias


Snarky

Hmmm, not sure what the confusion is.

Line 12 reads the game speed (40 or 60 or whatever) into the variable gameSpeed. The game speed is how many cycles ("frames") AGS runs per second.

Lines 16-36 are in the function repeatedly_execute_always(). This is a function that runs automatically once every game cycle. Therefore, it will run gameSpeed times (40 or 60 or whatever) per second.

You see that line 21 increments a variable called gameCounter. This variable will count up from 0, increasing by one every time the function runs. When it reaches the number gameSpeed (40, 60...), line 24 will become true, and gameCounter will be reset to 0 (line 26). In other words, this counter means that the block from line 25 to 35 will only be executed once every gameSpeed cycles. Since the game runs gameSpeed cycles per second, that should mean it runs the block once every second. Since the code in the block increments the game time by one minute, the effect should be that the game time moves forward by one minute every second. (If that's not what's happening, you may be doing something wrong, like changing the actual game speed after line 12.)

By changing how often the block is executed, you can change how fast the game time progresses. So make gameSpeed higher, and time will move slower. Make it lower, and time will move faster.

RenatoDias

The clock looks more like a stopwatch, increasing gamespeed only makes it faster. And I haven't modified the code you provided, I put it as it was.

Snarky

Well, I've made a test game, and it works correctly for me: https://www.dropbox.com/s/mrvwsvpts7prjo0/daynight%20test.rar

I put it in a module (TimeCycle) for convenience; the relevant code is:

Code: AGS

//In the TimeCycle module (only the relevant parts)

bool timeIsRunning = false;
int timeSpeed = 40;
int frameCounter = 0;
int dayCounter = 0;
int minuteCounter = 0;

function game_start()
{
  timeSpeed = GetGameSpeed();    // Default speed is 1 minute/second
}

function repeatedly_execute_always()
{
  if(timeIsRunning)
  {
    frameCounter++;
    if(frameCounter >= timeSpeed)
    {
      frameCounter = 0;
      minuteCounter++;
      
      if(minuteCounter >= MINUTES_PER_HOUR*HOURS_PER_DAY)
      {
        minuteCounter = 0;
        dayCounter++;
      }
    }
  }
}

// In globalscript
function repeatedly_execute_always() 
{
  // ...

  // All of the parts refer to various data access functions in the module
  lblStatus.Text = String.Format("%s, Day %d (%s) %02d:%02d",
                                  seasonString, 
                                  TimeCycle.GetDay(),
                                  sunStageString,
                                  TimeCycle.GetHour(),
                                  TimeCycle.GetMinute());
}

// In the first playable room script
function room_FirstLoad()
{
  TimeCycle.SetTime(1, 4, 55);    // Day 1 at 04:55
  TimeCycle.SetTimeSpeed(40);     // Adjust this to make game run faster/slower
  TimeCycle.SetTimeRunning(true); // Start the clock
  gStatus.Visible = true;         // Display the time label
}


All the data access methods and calculations to find the time of day, season etc. are rather tedious, so I'm not copying them here, but here's what the module header looks like:

Code: AGS

// TimeCycle module header
enum TimeCycle_Season
{
  TimeCycle_Spring=0,
  TimeCycle_Summer,
  TimeCycle_Fall,
  TimeCycle_Winter,
};

enum TimeCycle_SunStage
{
  TimeCycle_Dawn=0,
  TimeCycle_Day,
  TimeCycle_Dusk,
  TimeCycle_Night,
};

struct TimeCycle
{
  import static bool IsTimeRunning();
  import static void SetTimeRunning(bool run);
  import static int GetTimeSpeed();
  import static void SetTimeSpeed(int speed);
  
  import static int GetDay();
  import static int GetTime();
  import static int GetHour();
  import static int GetMinute();
  
  import static void SetTime(int day,  int hour,  int minute);
  
  import static TimeCycle_Season GetSeason();
  import static TimeCycle_SunStage GetSunStage();
  
  import static int GetSeasonPercent();
  import static int GetSunStagePercent();

  import static bool HasSunStageChanged();
  import static bool HasSeasonChanged();
};

import String TimeCycle_SeasonName[4];
import String TimeCycle_SunStageName[4];


... and a couple of examples from the module script:

Code: AGS

// TimeCycle module script

static int TimeCycle::GetTime()
{ return minuteCounter; }

static void TimeCycle::SetTime(int day,  int hour,  int minute)
{
  dayCounter = day;
  minuteCounter = minute + hour*MINUTES_PER_HOUR;
}

RenatoDias

Snarky, great one, works nicely as I tested. Do you have the project itself? You uploaded the compiled game only. Can you upload the project with all the AGS files? I promise I won't steal graphics and such.
Thanks.

SMF spam blocked by CleanTalk