Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Topics - eri0o

#121
A small plugin to open URL in a browser, made for AGS 3.6.0 and beyond. (the plugin uses SDL2, so if you are using a previous version of ags, you have to add SDL2 somewhere)

agsappopenurl_windows.zip | agsappopenurl_linux.tar.gzagsappopenurl_macos.zip

You call it like this:

Code: ags
AppOpenURL(eAUrlProto_https, "itch.io");

And it should open the itch website in a browser tab.

The idea is to use this plugin instead of the agsshell plugin - for safety reasons and better cross-platform support. It uses SDL2 behind the scenes, so it can't run in older versions of AGS. (well you would have to add SDL2 somewhere if using an old ags version)
#122
Hey, there have been some newcomer/bot/weird people digging some threads where the last post has been +5yr old.

I think when the thread itself (first post), it's alright to keep going, and maybe either some exception or update to the pinned posts. But for the rest, there is usually little sense to digging these really old threads - the moderator could be able to unlock, since we have some people that come back to their game after a long time.

Most of the time I have seen some post in these old threads it has been the spam bots that make some random post with a single link to whatever the website they are spamming. Maybe this could reduce being targeted by these boys a bit?
#123
So, I am trying to figure out how to travel between cities, and it appears Italy has a pretty good railway system.

But I can't understand the immense amount of different tickets, if I should get some type of pass instead of individual tickets, and if booking in advance is really needed - it seems a lot of different trains are available.

Somehow it's really hard to find good information online.  :-\
#124
Being able to playback videos in AGS while using GUIs at the same time could enable building FMV games in AGS. Bonus points for having the video just run in any Overlay (regular overlays or room overlays), which would allow something similar to using videos for things in the background or some specifics, like say, a communication device in a GUI that receives a message.

From what I remember, when a video is playing AGS enters in a special state, so the screen is exclusively for the video and it can't process other things beyond the key to skip the video.

This as other special states are distributed throughout the code (see: Refactor AGS for 1 game loop and game state managing). It looks like some states could be instead managed through a Finite State Machine, but others are things we kinda want to just happen at the same frame (so not exactly a different state). This refactoring is not necessary, but if there was some similar way to arrange this it could maybe work. Video is a bit tricky that the sound and image has to be synchronous, so not sure that could workout.

This thread is both to discuss use cases in game and to see what would be needed in ags details, and also figuring out things like the API for this.

#125
I just played this game with a friend right now, we were sitting in sofas one across another and relaying what each of us were seeing to the other. The Lite version is 20-30min, but there is a paid version that is longer. We didn't had much time so we played the lite and it's pretty interesting.

https://play.google.com/store/apps/details?id=air.com.RustyLake.ThePastWithinLite

It was a similar experience to Keep Talking and Nobody Explodes.

Just starting a thread to accumulate a bunch of little games that are interesting, in case it's useful for you know... Inspiring and making games. :)
#126
I found the abstract of the chapter of a book online that apparently mentions the AGS interface. Leaving here in case someone wants to read it.

https://link.springer.com/chapter/10.1007/978-3-031-05214-9_11

The book is named The Authoring Problem, and the chapter in question is named Mapping the Unmappable: Reimagining Visual Representations of Interactive Narrative (doi 10.1007/978-3-031-05214-9_11)

Unfortunately there's something wrong with my institutional access, so I haven't been able to read it yet.

Edit: Being onsite my access to springer normalized. There's not much in the article, AGS is in a context of other text based narrative development software, so in this context it's interface is much more visual - as others (Twine, Inform7 and Storyspace 3) focus more on text.

This article gave me an idea for a simple tool that naively grabs all player.ChangeRoom in the room scripts, notes down the script room number and the destination room, and then produces a small graph of the rooms and their connections. While I have my guesses to do this in either python, R or JS, I am not sure if there are useful graphing libraries in C# so it could be cooked up as an Editor plugin, which is more elegant.

(of course change rooms in the global script and other non-room scripts would be missed, but I guess this would still have a little use to check your story development)
#127
So, I wanted to see if it was possible to do Wave Function Collapse in AGS Script. Because I was lazy, I decided to try to port the code of this C version here: https://github.com/krychu/wfc/blob/master/wfc.h

Now this did NOT work, it runs and takes some long time, but the result at the end is wrong... Anyhow, decided to leave my try it out here in case someone really wants to take the challenge. Probably the best approach is to actually read the papers and figure a strategy that is better suited for the concepts available in AGS.

wfc.ash
Code: ags
// new module header

#define MAX_TILES 1024

enum WfcDirection {
  eWfcDir_Up = eDirectionUp, 
  eWfcDir_Down = eDirectionDown, 
  eWfcDir_Left = eDirectionLeft, 
  eWfcDir_Right = eDirectionRight 
};

enum WfcMethod {
  eWfc_Overlapping, 
  eWfc_Tiled, 
};

managed struct WfcCell {
  int Tile[MAX_TILES];                  // Possible tiles in the cell (initially all)
  int TileCount;

  int FrequencySum; // Sum of tile frequencies used to calculate
                    // entropy and randomly pick a tile when
                    // collapsing a tile.

  float Entropy;    // Shannon entropy. Cell with the smallest entropy
                    // is picked to be collapsed next.
};

managed struct WfcProp {
  int SrcCellIdx;
  int DstCellIdx;
  WfcDirection direction;
};

struct Wfc {
  WfcMethod method;
  int seed;
  
  DynamicSprite* Input;
  int TileWidth;
  int TileHeight;  
  bool FlipTilesX;
  bool FlipTilesY;
  bool RotateTiles;
  
  DynamicSprite* TilesSprite[];
  int TilesFrequency[]; // count of tile occurrences in the input image.
  int TilesCount;
  
  /* output */
  
  DynamicSprite* Output;
  WfcCell* Cell[];
  int CellCount;
  int FrequencySum;
  
  /* in-use */
  
  WfcProp* Prop[];
  int PropCount;
  int PropIdx;
  
  int CollapsedCellCount;
  
  bool allowed_tiles[1048576];
  
  
  /* API */
  
  import void Init(DynamicSprite* output, int input_sprite, int tile_width, int tile_height, bool xflip_tiles = true, bool yflip_tiles = true, bool rotate_tiles = true, bool expand_input = false);
  import void Restart();
  import void RegenerateOutput();
  import void Run(int max_collapse_cnt);
  import void RepeatedlyUpdate(int times = 1);
  bool Generated;
  
  protected int _Rp_CellIdx;
  protected int _Rp_MaxCollapseCnt;
  
  import protected void _SwapTiles(int a_idx, int b_idx);  
  import protected void _RemoveDuplicatedTiles();
  
  import protected void _CreateProps();
  import protected void _CreateCells();
  import protected void _CreateTiles();
  
  import protected void _AddProp(int src_cell_idx, int dst_cell_idx, WfcDirection direction);
  import protected void _AddPropUp(int src_cell_idx);
  import protected void _AddPropDown(int src_cell_idx);
  import protected void _AddPropLeft(int src_cell_idx);
  import protected void _AddPropRight(int src_cell_idx);
  import protected bool _IsTileEnabled(int tile_idx, int cell_idx, WfcDirection direction);
  import protected bool _IsPropPending(int cell_idx, WfcDirection direction);
  
  import protected bool _PropagateProp(WfcProp* p);
  import protected bool _Propagate(int cell_idx);
  import protected bool _Collapse(int cell_idx);
  import protected int _NextCell();
  import protected void _InitCells();
   
  import protected void _ComputeAllowedTiles();
  import protected void _CreateTilesOverlapping(int tile_width, int tile_height, bool expand_image, bool xflip_tiles, bool yflip_tiles, bool rotate_tiles);
};

wfc.asc
Code: ags
// new module script

int _DirToIdx( WfcDirection direction)
{
  switch (direction) {
    case eWfcDir_Up:    return 0; break;
    case eWfcDir_Down:  return 1; break;
    case eWfcDir_Left:  return 2; break;
    case eWfcDir_Right: return 3; break;
  }
  return 0;
}

WfcDirection _IdxToDir(int d)
{
  switch (d) {
    case 0: return eWfcDir_Up; break;
    case 1: return eWfcDir_Down; break;
    case 2: return eWfcDir_Left; break;
    case 3: return eWfcDir_Right; break;
  }
  return eWfcDir_Up;
  
}

// Return 1 if the two images overlap perfectly except the edges in the given direction, 0 otherwise.
bool _SprCmpOverlap(DynamicSprite* dynspr_a, DynamicSprite* dynspr_b, WfcDirection direction)
{
  int a_offx, a_offy, b_offx, b_offy, width, height;

  switch (direction) {
    case eWfcDir_Up:
      a_offx = 0; a_offy = 0;
      b_offx = 0; b_offy = 1;
      width = dynspr_a.Width;
      height = dynspr_a.Height-1;
      break;
    case eWfcDir_Down:
      a_offx = 0; a_offy = 1;
      b_offx = 0; b_offy = 0;
      width = dynspr_a.Width;
      height = dynspr_a.Height-1;
      break;
    case eWfcDir_Left:
      a_offx = 0; a_offy = 0;
      b_offx = 1; b_offy = 0;
      width = dynspr_a.Width-1;
      height = dynspr_a.Height;
      break;
    case eWfcDir_Right:
      a_offx = 1; a_offy = 0;
      b_offx = 0; b_offy = 0;
      width = dynspr_a.Width-1;
      height = dynspr_a.Height;
      break;
    default:
      AbortGame("WFC Error: Invalid Direction");
      return false;
  }

  DrawingSurface* surf_a = dynspr_a.GetDrawingSurface();
  DrawingSurface* surf_b = dynspr_b.GetDrawingSurface();

  for (int y=0; y<height; y++) {
    for (int x=0; x<width; x++) {

      int a_x = a_offx + x;
      int b_x = b_offx + x;
      int a_y = a_offy + y;
      int b_y = b_offy + y;

      if(surf_a.GetPixel(a_x, a_y) != surf_b.GetPixel(b_x, b_y)) {
        return false;
      }
    }
  }
  
  surf_a.Release();
  surf_b.Release();
  
  return true;
}

 bool noloopcheck _SprEqual(DynamicSprite* dynspr_a, DynamicSprite* dynspr_b)
{
  if(dynspr_a.Width != dynspr_b.Width || dynspr_a.Height != dynspr_b.Height) {
    return false;
  }
  
  if(dynspr_a == dynspr_b) return true;
  
  int width = dynspr_a.Width;
  int height = dynspr_a.Height;
  
  DrawingSurface* surf_a = dynspr_a.GetDrawingSurface();
  DrawingSurface* surf_b = dynspr_b.GetDrawingSurface();
  
  for (int y=0; y<height; y++) {
    for (int x=0; x<width; x++) {
      if(surf_a.GetPixel(x, y) != surf_b.GetPixel(x, y)) {
        return false;
      }
    }
  }
  
  return true;
}

DynamicSprite* noloopcheck _GenerateOutput(int width, int height,  WfcCell* cells[], DynamicSprite* tiles[])
{
  DynamicSprite* spr = DynamicSprite.Create(width, height);
  DrawingSurface* surf = spr.GetDrawingSurface();
  
  for (int y=0; y<height; y++) {
    for (int x=0; x<width; x++) {
      WfcCell* cell = cells[y*width+x];
      
      int component = 0;
      for(int i=0; i<cell.TileCount; i++) {
        DynamicSprite* tile = tiles[cell.Tile[i]];
        DrawingSurface* tmp_surf = tile.GetDrawingSurface();
        component += tmp_surf.GetPixel(0, 0);
        tmp_surf.Release();
      }
      
      surf.DrawingColor = component/cell.TileCount;
      surf.DrawPixel(x, y);
    }
  }
  
  surf.Release();
  return spr;
}

void _AddOverlappingImages(DynamicSprite* tiles_dynspr[], int tiles_freq[], DynamicSprite* img, int xcnt, int ycnt, int tile_width, int tile_height)
{
  int tile_cnt = xcnt * ycnt;
  DrawingSurface* surf = img.GetDrawingSurface();
  
  for (int y=0; y<ycnt; y++) {
    for (int x=0; x<xcnt; x++) {
      int i = y*xcnt + x;
      
      tiles_freq[i] = 1;
      tiles_dynspr[i] = DynamicSprite.CreateFromDrawingSurface(surf, x, y, tile_width, tile_height);
    
    }
  }
}


void _AddFlippedImages(DynamicSprite* tiles_dynspr[], int tiles_freq[], int tile_idx, eFlipDirection flip_dir)
{
  for (int i=0; i<tile_idx; i++) {
    int src_idx = i;
    int dst_idx = tile_idx + i;
    
    tiles_freq[dst_idx] = 1;
    DynamicSprite* spr_src = tiles_dynspr[src_idx];
    tiles_dynspr[dst_idx] = DynamicSprite.CreateFromExistingSprite(spr_src.Graphic, true);
    DynamicSprite* spr_dst = tiles_dynspr[dst_idx];
    spr_dst.Flip(flip_dir);    
  }
}


void _AddRotatedImages(DynamicSprite* tiles_dynspr[], int tiles_freq[], int tile_idx)
{
  
  for (int i=0; i<tile_idx; i++) {
    int src_idx = i;
    DynamicSprite* src_spr = tiles_dynspr[src_idx];
    
    for (int j=0; j<3; j++) {
      
      int dst_idx = tile_idx + i*3 + j;
      tiles_freq[dst_idx] = 1;
      
      tiles_dynspr[dst_idx] = DynamicSprite.CreateFromExistingSprite(src_spr.Graphic, true);
      DynamicSprite* spr_dst = tiles_dynspr[dst_idx];
      
      if(j != 0) {
        spr_dst.Rotate(j*90);
      }
    }
  }
}

protected void Wfc::_SwapTiles(int a_idx, int b_idx)
{
  int tmp_freq = this.TilesFrequency[a_idx];
  DynamicSprite* tmp_spr = this.TilesSprite[a_idx];
  
  this.TilesFrequency[a_idx] = this.TilesFrequency[b_idx];
  this.TilesSprite[a_idx] = this.TilesSprite[b_idx];
  
  this.TilesFrequency[b_idx] = tmp_freq;
  this.TilesSprite[b_idx] = tmp_spr;
}

protected void Wfc::_RemoveDuplicatedTiles()
{
  int unique_cnt = 1;
  for (int j=1; j<this.TilesCount; j++) {
    bool unique = true;
    for (int k=0; k<unique_cnt; k++) {
      if(_SprEqual(this.TilesSprite[j], this.TilesSprite[k])) {
        unique = false;
        this.TilesFrequency[k]++;
        break;
      }
    }
    
    
    if (unique) {
      if (unique_cnt != j) {
        this._SwapTiles(j, unique_cnt);        
      }
      
      unique_cnt++;
    }
  }

  for(int i=unique_cnt; i<this.TilesCount; i++) {
    this.TilesFrequency[i] = 0;
    this.TilesSprite[i] = null;
  }
  
  this.TilesCount = unique_cnt;
}

#define WFC_MAX_PROP_CNT 1000

////////////////////////////////////////////////////////////////////////////////
//
// WFC: Solve (Method-independent)
//
////////////////////////////////////////////////////////////////////////////////


protected void noloopcheck Wfc::_CreateProps()
{
  int prop_max = this.CellCount * WFC_MAX_PROP_CNT;
  this.Prop = new WfcProp[prop_max];
  
  for(int i=0; i<prop_max; i++)
  {
    this.Prop[i] = new WfcProp;
  }
}

protected void noloopcheck Wfc::_CreateCells()  
{
  int cell_cnt = this.CellCount;
  this.Cell = new WfcCell[cell_cnt];
  
  for (int i=0; i<cell_cnt; i++)
  {
    WfcCell* cell = new WfcCell;
    this.Cell[i] = cell;

    if(i==0) continue;
    
    int tile_count = this.TilesCount;
    
    cell.TileCount = tile_count;
  }
}

protected void Wfc::_CreateTiles()
{
  int tile_cnt = this.TilesCount;
  this.TilesFrequency = new int[tile_cnt];
  this.TilesSprite = new DynamicSprite[tile_cnt];
  
  for (int i=0; i<this.TilesCount; i++) {
    this.TilesFrequency[i] = 0;
    this.TilesSprite[i] = null;
  }
}
  
protected void Wfc::_AddProp(int src_cell_idx, int dst_cell_idx, WfcDirection direction)
{
  WfcProp* prop = this.Prop[this.PropCount];
  
  prop.SrcCellIdx = src_cell_idx;
  prop.DstCellIdx = dst_cell_idx;
  prop.direction = direction;
  
  this.PropCount++;
}

protected void Wfc::_AddPropUp(int src_cell_idx)
{
  int dst_cell_idx = src_cell_idx - this.Output.Width;
  if(dst_cell_idx < 0) return; // can't go to upper cell,  in first line
  
  this._AddProp(src_cell_idx, dst_cell_idx, eWfcDir_Up);
}

protected void Wfc::_AddPropDown(int src_cell_idx)
{
  int dst_cell_idx = src_cell_idx + this.Output.Width;
  
  if(dst_cell_idx > this.CellCount) return; // can't go to down cell,  in last line
  
  this._AddProp(src_cell_idx, dst_cell_idx, eWfcDir_Down);    
}

protected void Wfc::_AddPropLeft(int src_cell_idx)
{
  int dst_cell_idx = src_cell_idx - 1;
  
  if(src_cell_idx % this.Output.Width == 0) return;
  
  this._AddProp(src_cell_idx, dst_cell_idx, eWfcDir_Left);   
}

protected void Wfc::_AddPropRight(int src_cell_idx)
{
  int dst_cell_idx = src_cell_idx + 1;
  
  if(src_cell_idx % this.Output.Width == this.Output.Width - 1) return;
    
  this._AddProp(src_cell_idx, dst_cell_idx, eWfcDir_Right);
}

protected bool Wfc::_IsTileEnabled(int tile_idx, int cell_idx, WfcDirection direction)
{
  WfcCell* cell = this.Cell[cell_idx];
  int tile_cnt = this.TilesCount;

  for(int i=0, cnt=cell.TileCount; i<cnt; i++)
  {
    if(this.allowed_tiles[_DirToIdx(direction)*this.TilesCount+tile_idx]) return true;
  }
  
  return false;
}

// Checks whether particular prop is already added and pending, in which
// case there is no point of adding the same prop again.
protected bool Wfc::_IsPropPending(int cell_idx, WfcDirection direction)
{
  for (int i=this.PropIdx+1; i < this.PropCount; i++) {
    WfcProp* prop = this.Prop[i];
    
    if(prop.SrcCellIdx == cell_idx && prop.direction == direction) return true;    
  }
  
  return false;
}


// Updates tiles in the destination cell to those that are allowed by the source cell
// and propagate updates
protected bool Wfc::_PropagateProp(WfcProp* p)
{
  int new_cnt = 0;
  
  WfcCell* dst_cell = this.Cell[p.DstCellIdx];
  
  // Go through all destination tiles and check whether they are enabled by the source cell
  for (int i=0, cnt=dst_cell.TileCount; i<cnt; i++) {
    int possible_dst_tile_idx = dst_cell.Tile[i];
    
    // If a destination tile is enabled by the source cell, keep it
    if(this._IsTileEnabled(possible_dst_tile_idx, p.SrcCellIdx, p.direction)) {
      dst_cell.Tile[new_cnt] = possible_dst_tile_idx;
      new_cnt++;
    } else {
      int freq = this.TilesFrequency[possible_dst_tile_idx];
      float p_f = IntToFloat(freq) / IntToFloat(this.FrequencySum);
      
      dst_cell.Entropy += p_f*Maths.Log(p_f);
      dst_cell.FrequencySum -= freq;
      
      if(dst_cell.FrequencySum == 0) return false;
    }
  }
  
  if(new_cnt == 0) return false;
  
  if(dst_cell.TileCount != new_cnt) {
    int dst_cell_i = p.DstCellIdx;
    if(new_cnt == 1) this.CollapsedCellCount++;
    if(p.direction != eWfcDir_Down && this._IsPropPending(dst_cell_i, eWfcDir_Up)) this._AddPropUp(dst_cell_i);
    if(p.direction != eWfcDir_Up && this._IsPropPending(dst_cell_i, eWfcDir_Down)) this._AddPropDown(dst_cell_i);
    if(p.direction != eWfcDir_Right && this._IsPropPending(dst_cell_i, eWfcDir_Left)) this._AddPropLeft(dst_cell_i);
    if(p.direction != eWfcDir_Left && this._IsPropPending(dst_cell_i, eWfcDir_Right)) this._AddPropRight(dst_cell_i);    
  }
  
  dst_cell.TileCount = new_cnt;
  
  return true;
}

protected bool Wfc::_Propagate(int cell_idx)
{
  this.PropCount = 0;
  
  this._AddPropUp(cell_idx);
  this._AddPropDown(cell_idx);
  this._AddPropLeft(cell_idx);
  this._AddPropRight(cell_idx);
  
  for(int i=0; i<this.PropCount; i++)
  {
    this.PropIdx = i;
    WfcProp* p = this.Prop[i];
    
    if(!this._PropagateProp(p)) {
      return false;
    }
  }
  
  return true;
}


protected bool Wfc::_Collapse(int cell_idx)
{
  WfcCell* cell = this.Cell[cell_idx];
  
  int remaining = Random(cell.FrequencySum);
  
  for(int i=0; i<cell.TileCount; i++) {
    int tile_idx = cell.Tile[i];
    int freq = this.TilesFrequency[tile_idx];
    
    if(remaining >= freq) {
      remaining -= freq;
    } else {
      cell.Tile[0] = cell.Tile[i];
      cell.TileCount = 1;
      cell.FrequencySum = 0;
      cell.Entropy = 0.0;
      this.CollapsedCellCount++;
      return true;
    }
  }
  
  return false;  
}

protected int Wfc::_NextCell()
{
  int min_idx = -1;
  float min_entropy = 3402823466.0;
  
  for (int i=0; i<this.CellCount; i++) {    
    WfcCell* cell = this.Cell[i];
    
    // Add small noise to break ties between tiles with the same entropy
    float entropy = cell.Entropy + (IntToFloat(Random(4096))/8388608.0);
    
    
    if (cell.TileCount != 1 && entropy < min_entropy) {
      min_entropy = entropy;
      min_idx = i;
    }
  }
  
  return min_idx;
}

protected void noloopcheck Wfc::_InitCells()
{
  int sum_freqs = 0;
  
  for(int i=0; i<this.TilesCount; i++) {
    sum_freqs += this.TilesFrequency[i];
  }
  
  this.FrequencySum = sum_freqs;
  
  float sum_plogp = 0.0;
  for (int i=0; i<this.TilesCount; i++) {
    float p_f = IntToFloat(this.TilesFrequency[i])/IntToFloat(sum_freqs);
    sum_plogp += p_f*Maths.Log(p_f);
  }
  
  float entropy = -sum_plogp;
  
  for(int i=0; i<this.CellCount; i++)
  {
    WfcCell* cell = this.Cell[i];
    
    cell.TileCount = this.TilesCount;
    cell.FrequencySum = sum_freqs;
    cell.Entropy = entropy;
    
    for(int j=0; j<this.TilesCount; j++) {
      cell.Tile[j] = j;
    }    
  }
  
  this.PropCount = 0;
}

protected void noloopcheck Wfc::_ComputeAllowedTiles()
{
  int tile_cnt = this.TilesCount;
  
  for (int d=0; d<4; d++) {
    for (int i=0; i<tile_cnt; i++) {
      for (int j=0; j<tile_cnt; j++) {
        int idx = d*i*tile_cnt+j;
        
        this.allowed_tiles[idx] = _SprCmpOverlap(this.TilesSprite[i], this.TilesSprite[j], _IdxToDir(d));
      }
    }
  }
}

protected void Wfc::_CreateTilesOverlapping(int tile_width, int tile_height, bool expand_image, bool xflip_tiles, bool yflip_tiles, bool rotate_tiles)
{
  int xcnt = this.Input.Width - tile_width + 1;
  int ycnt = this.Input.Height - tile_height + 1;
  
  if(expand_image) {
    xcnt = this.Input.Width;
    ycnt = this.Input.Height;
    int w = this.Input.Width;
    int h = this.Input.Height;
    this.Input.Resize(w + tile_width - 1, h + tile_height -1);
  }
  
  int tile_cnt = xcnt*ycnt;
  
  if(xflip_tiles) tile_cnt *= 2;    
  if(!(xflip_tiles && rotate_tiles) && yflip_tiles) tile_cnt *= 2;
  if(rotate_tiles) tile_cnt *= 4;
  
  this.TilesCount = tile_cnt;
  this._CreateTiles();
  
  _AddOverlappingImages(this.TilesSprite, this.TilesFrequency, this.Input, xcnt, ycnt, tile_width, tile_height);
  
  int base_tile_cnt = xcnt * ycnt;
  
  if(xflip_tiles) {
    _AddFlippedImages(this.TilesSprite, this.TilesFrequency, base_tile_cnt, eFlipLeftToRight);
    base_tile_cnt *= 2;
  }
  
  if(!(xflip_tiles && rotate_tiles) && yflip_tiles) {
    _AddFlippedImages(this.TilesSprite, this.TilesFrequency, base_tile_cnt, eFlipUpsideDown);
    base_tile_cnt *= 2;
  }
  
  if(rotate_tiles) {
    _AddRotatedImages(this.TilesSprite, this.TilesFrequency, base_tile_cnt);
    base_tile_cnt *= 4;
  }
  
  this._RemoveDuplicatedTiles();
}

void Wfc::Restart()
{
  this.CollapsedCellCount = 0;
  this.Generated = false;
  this._InitCells();
}

void Wfc::Init(DynamicSprite* output, int input_sprite, int tile_width, int tile_height, bool xflip_tiles, bool yflip_tiles, bool rotate_tiles, bool expand_input)
{
  this.Output = output;
  this.Input = DynamicSprite.CreateFromExistingSprite(input_sprite, true);
  this.method = eWfc_Overlapping;
  this.CellCount = this.Output.Width * this.Output.Height;
  this.TileWidth = tile_width;
  this.TileHeight = tile_height;
  this.TilesCount = 0;
  this.FlipTilesX = xflip_tiles;
  this.FlipTilesY = yflip_tiles;
  this.RotateTiles = rotate_tiles;
  
  this._CreateTilesOverlapping(tile_width, tile_height, expand_input, xflip_tiles, yflip_tiles, rotate_tiles);
  
  this._ComputeAllowedTiles();
  
  this._CreateCells();
  this._CreateProps();
    
  this.Restart();
}

void Wfc::RegenerateOutput()
{
  DynamicSprite* out = _GenerateOutput(this.Output.Width, this.Output.Height, this.Cell, this.TilesSprite);
  
  DrawingSurface* surf = this.Output.GetDrawingSurface();
  
  surf.Clear();
  surf.DrawImage(0, 0, out.Graphic);
  surf.Release();
}

void Wfc::Run(int max_collapse_cnt)
{
  int cell_idx = Random(this.Output.Height * this.Output.Width);
  this._Rp_CellIdx = cell_idx;
  this._Rp_MaxCollapseCnt = max_collapse_cnt;
}

void Wfc::RepeatedlyUpdate(int times)
{
  for(int i=0; i<times; i++) {
    int cell_idx = this._Rp_CellIdx;
    int max_collapse_cnt = this._Rp_MaxCollapseCnt;
    
    if(cell_idx == -1 || this.CollapsedCellCount == max_collapse_cnt) {
      this.Generated = true;
      return;
    }
    
    if(!this._Collapse(cell_idx)) return;  
    if(!this._Propagate(cell_idx)) return;
    
    cell_idx = this._NextCell();
    this._Rp_CellIdx = cell_idx;
    
    if(cell_idx == -1 || this.CollapsedCellCount == max_collapse_cnt) {
      this.Generated = true;
      return;
    }
  }
}

Usage

Code: ags
Wfc wfc;
Overlay* ovr;

function room_RepExec()
{
  wfc.RepeatedlyUpdate(8192);
  
  if(wfc.Generated) {
    if(ovr == null) {
      wfc.RegenerateOutput();
      ovr = Overlay.CreateGraphical(32, 32, wfc.Output.Graphic, true);
    }
  }
}

function room_Load()
{
  wfc.Init(DynamicSprite.Create(48, 48, true), 1, 3, 3, true, true, true, false);
  wfc.Run(2048);
  
}

function room_AfterFadeIn()
{

}
#128
Don't Give Up the Cat


Tonight, you are out in the woods. As a cat.

A short game intended to be played at night, using headphones. In this first-person game you use your mouse to look around and scratch things, and WASD keys for walking, shift to run, and you use Esc to show the menu.
After you run you may sit around to breathe a bit and listen to the forest.


Game by eri0o
Music by Jon Paul Sapsford
Ghosts, graves and icon by Haloa
Gameplay Testing by Morgan Willcock, Heltenjon and Newwaveburritos

AGS Script Modules
Tween by Edmundito
Timer and Typed Text by  Crimson Wizard

Additional assets from itch io, opengameart and freesound
Forest trees, stones, flowers and base palette: Trixie (trixelized) - STRINGSTAR FIELDS
Clouds: ansimuz - Sunnyland,  recolored
House: Lanthanum - 3D Model House (PicoCAD),  recolored
Save cat: kotnaszynce - kitka.gif,  recolored+resized
Smoke: KnoblePersona - Smoke & Fire Animated Particle
Title Screen Cat: Ben - Camp Cat (PicoCAD), modified
Forest crickets and atmosphere: LokiF - Swamp Environment Audio
Cat Footsteps: melle_teich - Animal footsteps on dry leaves
Cat Jump: FOX2814 - Cat jump
Game Font: Fibberish by  Nathan Scott

This is a MAGS entry for the theme "Ghost", see original submission here.

The OST
#129


A friend made me this. Twice. I am thinking about what to do with the extra one I got! It's 3D printed and coated with some paint, pixel blue cup and matching keychain-ags tea bag - it has the pixel blue cup from the manual on the other side of the keychain too!
#130


Hey, I am starting to play around with Sprite Stacking in AGS. Has someone ever done this here?

Sliced.ash
Code: ags
// new module header
#define MAX_SLICED_OBJECTS 32
#define MAX_OVERLAYS 2048
#define OVERLAYS_STEP 32

managed struct SlicedObject {
  import void Set(float x, float y, float z, int graphic_first_slice, int graphic_last_slice);
  import static SlicedObject* Create(float x, float y, float z, int graphic_first_slice, int graphic_last_slice); // $AUTOCOMPLETEIGNORESTATIC$
  writeprotected int GraphicFirstSlice, GraphicLastSlice, SliceRange, SliceWidth, SliceHeight;
  writeprotected float X, Y, Z;
};

struct SlicedWorld{
  import void AddSlicedObject(int x, int y, int z, int graphic_first_slice, int graphic_last_slice);
  import void Render(float angle_radians = 0.0);
  protected SlicedObject* SlicedObjects[MAX_SLICED_OBJECTS];
  protected int ObjectCount;
  protected Overlay* Overlays[MAX_OVERLAYS];
};

Sliced.asc
Code: ags
// new module script
// -- SLICED OBJECT --

void SlicedObject::Set(float x, float y, float z, int graphic_first_slice, int graphic_last_slice)
{
  this.X = x;
  this.Y = y;
  this.Z = z;
  this.GraphicFirstSlice = graphic_first_slice;
  this.GraphicLastSlice = graphic_last_slice;
  this.SliceRange = graphic_last_slice - graphic_first_slice;
  this.SliceWidth = Game.SpriteWidth[graphic_first_slice];
  this.SliceHeight = Game.SpriteHeight[graphic_first_slice];
}

static SlicedObject* SlicedObject::Create(float x, float y, float z, int graphic_first_slice, int graphic_last_slice)
{
  SlicedObject* sobj = new SlicedObject;
  sobj.Set(x, y, z, graphic_first_slice, graphic_last_slice);
  return sobj;
}

// -- SLICED WORLD --

void SlicedWorld::AddSlicedObject(int x, int y, int z, int graphic_first_slice, int graphic_last_slice)
{
  SlicedObject* sobj = SlicedObject.Create(IntToFloat(x), IntToFloat(y), IntToFloat(z), graphic_first_slice, graphic_last_slice);
  
  this.SlicedObjects[this.ObjectCount] = sobj;
  this.ObjectCount++;
}


void SlicedWorld::Render(float angle_radians)
{
  float pre_cos = Maths.Cos(angle_radians);
    float pre_sin = - Maths.Sin(angle_radians);
  
  for(int i=0; i<this.ObjectCount; i++)
  {
    SlicedObject* sobj = this.SlicedObjects[i];
    
    int overlay_id_start = i*OVERLAYS_STEP;
    int slice_range = sobj.SliceRange;
    
    for(int j=0; j<slice_range; j++) {
      int overlay_id = overlay_id_start + j;
      if(this.Overlays[overlay_id] == null || !this.Overlays[overlay_id].Valid) {
        this.Overlays[overlay_id] = Overlay.CreateRoomGraphical(0, 0, sobj.GraphicFirstSlice + j);
      }
      
      Overlay* ovr = this.Overlays[overlay_id];
      
      float dist = IntToFloat(j) + sobj.Z;
      
      float lx = dist * pre_cos;
      float ly = dist * pre_sin;
      
      ovr.X = FloatToInt(sobj.X - lx);
      ovr.Y = FloatToInt(sobj.Y - ly);
      ovr.Rotation = Maths.RadiansToDegrees(angle_radians);
    }
  }
}

Usage code

Code: ags
#define SPR_BARREL 1
#define SPR_BARREL_LAST 8
#define SPR_CHAIR 9
#define SPR_CHAIR_LAST 19
#define SPR_CHEST 20
#define SPR_CHEST_LAST 30

SlicedWorld sWorld;

function room_AfterFadeIn()
{

}

function room_Load()
{
  sWorld.AddSlicedObject(32, 32, 0, SPR_BARREL, SPR_BARREL_LAST);
  sWorld.AddSlicedObject(64, 32, 0, SPR_BARREL, SPR_BARREL_LAST);
  sWorld.AddSlicedObject(64, 96, 0, SPR_CHAIR, SPR_CHAIR_LAST);
  sWorld.AddSlicedObject(128, 96, 0, SPR_CHAIR, SPR_CHAIR_LAST);
  sWorld.AddSlicedObject(200, 48, 0, SPR_CHEST, SPR_CHEST_LAST);
  sWorld.AddSlicedObject(230, 48, 0, SPR_CHEST, SPR_CHEST_LAST);
  sWorld.AddSlicedObject(160, 96, 0, SPR_CHEST, SPR_CHEST_LAST);
  sWorld.AddSlicedObject(32, 111, 0, SPR_CHEST, SPR_CHEST_LAST);
}

float angl;
function room_RepExec()
{
  angl += 0.0125;
  sWorld.Render(angl);
}

So far my code and usage is like so, but I am not sure on this design. Any ideas?

(link to above project zip file to download)
#131
DistFX version 0.2.0

Get Latest Release distfx.scm | GitHub Repo | Project with Demo!

AGS Script Module for Distortion Effects, based on Earthbound Battle Backgrounds.



Play with the demo!

Usage

In a room script, link before fade in and repeatedly execute, and try the example below.

Code: ags
DistFX fx; // somewhere with the same lifetime as the surface owner while distorted
Overlay* ovr;
DynamicSprite* spr;

function room_RepExec()
{
  fx.Update(Room.GetDrawingSurfaceForBackground(), spr.GetDrawingSurface(), 2 /* effect */);
  ovr.Graphic = spr.Graphic;
}

function room_Load()
{
  if(ovr == null) {
    spr = DynamicSprite.CreateFromBackground();
    ovr = Overlay.CreateGraphical(0, 0, spr.Graphic, true);
  }
}

Original Earthbound effects used a per pixel approach, but due to how AGS Script drawing performs and works, this module uses a tile based approach.

Script API

DistFX.Update
Code: ags
void DistFX.Update(DrawingSurface* source, DrawingSurface* dest, int effect);
Draws from a source surface to a destination surface using a distortion effect, from the effect bank. Currently, the available effects range is 1-135. Effect 0 appears as no effect but still goes through all the effect pipeline - and will use CPU resources the same.

DistFX.Reset
Code: ags
void DistFX.Reset();
Reset internal state, use on state change.

DistFX.DrawingTransparency
Code: ags
attribute int DistFX.DrawingTransparency;
Drawing Transparency, use for blurring the effects. Default is 0, range from 0 to 99.

DistFX.TileWidth
Code: ags
attribute int DistFX.TileWidth;
Distortion Tile Width, factor of source width, bigger is less resource intensive. Default is 64 pixels.

DistFX.TileHeight
Code: ags
attribute int DistFX.TileHeight;
Distortion Tile Height, factor of source height, bigger is less resource intensive. Default is 1 pixel.

License

This code is licensed with MIT LICENSE.
#132
Experimental Editor with AGS with Gamepad support!

Download Editor Here: >>AGS-3.99.106.0-Alpha3-JoystickPR.zip<< | installer | Code Source | GitHub Issue | GitHub PR

First the concepts, joystick means a generic input device comprised by binary (button) and analog (axis) inputs. A Gamepad means something that is vaguely close to a Xbox360 controller.

We can expand the API later, but the basics is joystick connection and disconnection is handled inside the AGS Engine and we can skip things by pressing A,B,X,Y or the symbols in PS controller.



@Alan v.Drake made a beautiful test project!



Playable AgsGameGamepadV4.zip | project | Online https://ericoporto.github.io/agsjs/gamepadtest/




Joystick Static Methods

Joystick.JoystickCount
Code: ags
static readonly int Joystick.JoystickCount
Get the number of connected joysticks. No joysticks should return 0!


Joystick.Joysticks
Code: ags
static readonly Joystick* Joystick.Joysticks[int index]
Gets a joystick by index from the internal engine joystick list.


Joystick Instance Attributes and Methods

Joystick.IsConnected
Code: ags
readonly bool Joystick.IsConnected
True if joystick is connected.


Joystick.Name
Code: ags
readonly String Joystick.Name
joystick name.



Joystick.IsButtonDown
Code: ags
bool Joystick.IsButtonDown(int button)
checks if a joystick button is pressed, by index. DPad is usually mapped separately as a hat.


Joystick.GetAxis
Code: ags
bool Joystick.GetAxis(int axis, optional float deadzone)
get a joystick axis or trigger, trigger only has positive values, by axis number. Values varies from -1.0 to 1.0 for axis, and 0.0 to 1.0 for triggers.


Joystick.GetHat
Code: ags
eJoystick_Hat Joystick.GetHat(int hat)
returns hat value


Joystick.AxisCount
Code: ags
readonly int Joystick.AxisCount
get the number of axis in the joystick


Joystick.ButtonCount
Code: ags
readonly int Joystick.ButtonCount
get the number of buttons in the joystick


Joystick.HatCount
Code: ags
readonly int Joystick.HatCount
get the number of hats in the joystick



Joystick.IsGamepad
Code: ags
readonly bool Joystick.IsGamepad
True if joystick is a valid gamepad connected - this means SDL2 recognized it as a valid GameController and has successfully mapped it's buttons to an Xbox360 gamepad.


Joystick.IsGamepadButtonDown
Code: ags
bool Joystick.IsGamepadButtonDown(eGamepad_Button button)
checks if a gamepad button is pressed, including dpad.

Possible buttons:
  • eGamepad_ButtonA
  • eGamepad_ButtonB
  • eGamepad_ButtonX
  • eGamepad_ButtonY
  • eGamepad_ButtonBack
  • eGamepad_ButtonGuide
  • eGamepad_ButtonStart
  • eGamepad_ButtonLeftStick
  • eGamepad_ButtonRightStick
  • eGamepad_ButtonLeftShoulder
  • eGamepad_ButtonRightShoulder
  • eGamepad_ButtonDpadUp
  • eGamepad_ButtonDpadDown
  • eGamepad_ButtonDpadLeft
  • eGamepad_ButtonDpadRight


Joystick.GetGamepadAxis
Code: ags
float Joystick.GetGamepadAxis(eGamepad_Axis axis, optional float deadzone)
get gamepad axis or trigger, trigger only has positive values. Values varies from -1.0 to 1.0 for axis, and 0.0 to 1.0 for triggers.

You can optionally pass an additional parameter to use as deadzone. If an axis absolute value is smaller than the value of deadzone, it will return 0.0. Default value is AXIS_DEFAULT_DEADZONE, which is for now 0.125, use the name if you need a number.

Possible axis and triggers:
  • eGamepad_AxisLeftX
  • eGamepad_AxisLeftY
  • eGamepad_AxisRightX
  • eGamepad_AxisRightY
  • eGamepad_AxisTriggerLeft
  • eGamepad_AxisTriggerRight


CHANGELOG:
  • v1: initial release
  • v2: GetAxis now returns a float
  • v3: GetAxis now has an additional dead_zone parameter. Default value is GAMEPAD_DEFAULT_DEADZONE, which is for now 0.125, use the name if you need a number.
  • v4: Using correspondent A,B,X,Y buttons can skip Speech and Display messages, as long as you Connect to the Gamepad.
  • v5: ditched Gamepad-only approach for a Joystick first approach similar to löve.
  • v6: connection and disconnection handled in-engine, new AGS 4 approach.
  • v7: api renamed get,getcount to instead retrieve from array of joysticks, also fixed save/load game bug
#133
AGS Toolbox🧰 version 0.4.0

Get Latest Release agstoolbox.exe | companion atbx.exe | GitHub Repo



Hi, I made something intended for those that deal with multiple versions of AGS Editors and multiple AGS Game Projects. Place the agstoolbox.exe in a folder under your user, like "C:\Users\MY_USER\software", before you run it.



After you run the agstoolbox.exe, you will find a bluecup in your indicator area of your taskbar, near the clock area. Double click or right click it, to open the main panel.

Features
  • Editors that you install using AGS Toolbox are called Managed Editors, as they are managed through the tool. Just double click in any Editor available to Download to get one.
  • Editors you have acquired through other means (like installed through Chocolatey), are called Externally Installed Editors, directories to look for can be configured in the Settings.
  • Game Projects are looked for in directories set in the Settings. It will understand the Editor Version it was used to create, open in it by simply double clicking. You can also use right click to open in a different version.
  • You can add it to your Windows initialization if you want a quick shortcut to AGS game development (it's in the settings menu)

Right clicking any item on the list will show available actions, double clicking will execute the command marked in bold.

I made it initially for myself to help handle my own games and modules. It will, in future, have an additional pair tool, that will provide the same functionalities through a command line interface - intended for CI and automation.

For people in Unity, this may be a similar AGS version of the Unity Hub. I actually modeled it on the JetBrains Toolbox, which I use to manage different versions of CLion, Android Studio and PyCharm - it's also developed in PyCharm!

Command Line Usage

NOTE: On Windows, due to OS and PyInstaller limitations, agstoolbox.exe doesn't work with command line arguments, so atbx.exe is for exclusive command line usage.

Code: bash
$ atbx --help
usage: atbx [-h] [-s {bash,zsh,tcsh}] [-v] {list,install,open,build,settings} ...

agstoolbox is an application to help manage AGS Editor versions.

positional arguments:
  {list,install,open,build,settings}
                        command
    list                lists things
    install            install tools
    open                open an editor or project
    build              build an ags project
    settings            modify or show settings

optional arguments:
  -h, --help            show this help message and exit
  -s {bash,zsh,tcsh}    print shell completion script
  -v, --version        get software version.

Copyright 2023 Erico Vieira Porto, MIT.

As an example, a command line to force install the latest 3.6 AGS Editor, as a managed Editor is as follows

Code: bash
$ atbx install editor 3.6 -f
Will install managed AGS Editor release 3.6.0.47
 Downloading... 40475597/40475597 B |████████████████████████████████| AGS-3.6.0.47.zip
Extracting...
Installed release 3.6.0.47

The command line interface is working, but it is still quite limited, if you have more needs for it, please ask me!

Tab completion is also provided, the script for it can be generated with -s parameter, if you need help setting up just ask.



Experimentally, AGS Toolbox is also available on PyPI. Prefer the exe releases above for now, the PyPI releases are intended for uses of it's core parts and the atbx command line utility in a future continuous integration tool.



AGS Toolbox is written in Python, so if you are interested in a new feature and want to contribute code, just ask me and I can explain the basics of it.
#134
Modules, Plugins & Tools / MODULE: mode7 0.3.0
Tue 29/03/2022 02:11:31
mode7 version 0.3.0

Get Latest Release mode7.scm | GitHub Repo | Download project .zip

AGS Script Module for Mode7 like graphics. See demo in here! (Use Firefox, Safari or Chrome 100+, WASD to move)



This module allows you to project a sprite on the screen with the visual aspect people are familiar from the mode7 graphics of SNES games. Use the Mode7 struct for that!

If you want to do more, and also want to have other elements in it, similar to Mario Kart, you can leverage Mode7World and Mode7Object functionalities.

This code is based on original code that was written by Khris and presented in this ags forum thread. I asked Khris for the original code, which was very Kart oriented, I refactored to what I thought was more generic and made this module in the hopes people could pick around the code and make games out of it!

A note, the original code was a bit more performant, I pulled out some specific optimizations to make the code more flexible.

Script API
Spoiler


Mode7

Mode7.SetCamera
Code: ags
void Mode7.SetCamera(float x, float y, float z, float xa, float ya, float focal_length)

Sets the camera position, angle and focal length.

Mode7.SetViewscreen
Code: ags
void Mode7.SetViewscreen(int width, int height, optional int x,  optional int y)

Sets the screen area it will draw in.

Mode7.SetGroundSprite
Code: ags
void Mode7.SetGroundSprite(int ground_graphic)

Sets the ground sprite, this is the mode7 rendered sprite.

Mode7.SetHorizonSprite
Code: ags
void Mode7.SetHorizonSprite(int horizon_graphic, eHorizonType = eHorizonDynamic)

Sets a sprite that will roll around in the horizon, you can also make it static.

Mode7.SetBgColor
Code: ags
void Mode7.SetBgColor(int bg_color)

Sets the color of the background where the ground sprite doesn't reach.

Mode7.SetSkyColor
Code: ags
void Mode7.SetSkyColor(int sky_color)

Sets the color of the sky.

Mode7.TargetCamera
Code: ags
void Mode7.TargetCamera(float target_x, float target_y, float target_z, float teta_angle, eCameraTargetType camType = eCameraTarget_FollowBehind, bool is_lazy = true)

Target the camera to something.

Mode7.Draw
Code: ags
void Mode7.Draw()

Draws the ground sprite and horizon rendered in the Screen sprite.

Mode7.ResetGround
Code: ags
void Mode7.ResetGround()

Clears the screen sprite.

Mode7.CameraAngleX
Code: ags
float Mode7.CameraAngleX

The camera angle that is normal to the ground plane (e.g.: up and down).

Mode7.CameraAngleY
Code: ags
float Mode7.CameraAngleY

The camera angle that is on the ground plane (e.g.: left and right).

Mode7.Screen
Code: ags
DynamicSprite* Mode7.Screen

The Dynamic Sprite that represents the screen where the Mode7 ground is draw to.




Mode7World
This is an extension of Mode7 and gives you tools to present billboard sprites, positioned in the world coordinates, using the concept of Mode7Objects. You don't have to use it to do the drawing, but it should help you if you want to!

Mode7World.AddObject
Code: ags
Mode7Object* Mode7World.AddObject(int x, int z, float factor, int graphic)

Adds an object, and sets it's x and z position. The y (vertical position) is always zero. You also must pass a scale factor and it's graphics.

Mode7World.AddExternalObject
Code: ags
void Mode7World.AddExternalObject(int x, int z, float factor, int graphic)

Adds an external object that is not managed by the Mode7World. It will still be updated and draw, but removing it from the world will probably not garbage collect it.

Mode7World.RemoveObject
Code: ags
void Mode7World.RemoveObject(int object_i = -1)

Remove a specific object from the world by it's index. If you don't pass a value, it will remove the last valid object added.

Mode7World.RemoveAllsObjects
Code: ags
void Mode7World.RemoveAllsObjects()

Removes all objects from the Mode7 World.

Mode7World.GetAngleObjectAndCamera
Code: ags
int Mode7World.GetAngleObjectAndCamera(Mode7Object* m7obj)

Returns the angle in degrees between the camera and whatever angle is set to a specific object, pointed by their index. Useful when you want to change the graphic of an object based on their relative angle.

Mode7World.UpdateObjects
Code: ags
void Mode7World.UpdateObjects(optional bool do_sort)

Update the screen transform of all objects world positions to their screen positions. You must call it before drawing any objects! You can optionally skip any sorting if you plan rendering using overlays later, since they have zorder property which will be used later by the graphics driver.

Mode7World.DrawObjects
Code: ags
void Mode7World.DrawObjects()

Draws only the objects in the screen sprite. You can use when you need to draw additional things between the ground and the objects. Or when you don't need the ground at all.

Mode7World.DrawObjectsOverlay
Code: ags
void Mode7World.DrawObjectsOverlay()

Draws objects as overlays, without rasterizing at the screen sprite.

Mode7World.DrawWorld
Code: ags
void Mode7World.DrawWorld()

Draws the ground sprite and the objects over it, in the screen sprite.

Mode7World.DrawWorld2D
Code: ags
DynamicSprite* Mode7World.DrawWorld2D()

Gets a dynamic sprite with the world draw in top down view, useful for debugging.

Mode7World.Objects
Code: ags
writeprotected Mode7Object* Mode7World.Objects[i]

Let's you access a specific object in the mode7 world by it's index. Make sure to access a valid position.

Mode7World.ObjectCount
Code: ags
writeprotected int Mode7World.ObjectCount

Gets how many objects are currently in the mode7 world.

Mode7World.ObjectScreenVisibleCount
Code: ags
writeprotected int Mode7World.ObjectScreenVisibleCount

Gets how many objects are actually visible in the screen.

You can iterate through all the screen objects as follows:

Code: ags
for(int i=0; i < m7w.ObjectScreenVisibleCount; i++)
{
  // will make sure to access in order from far to closer
  int index = m7w.ObjectScreenVisibleID[m7w.ObjectScreenVisibleOrder[i]];
  Obj* m7object = m7w.Objects[index];
  
  // do as you you must with you m7object ...
}





Mode7Object
A Mode7Object, you should create objects by using Mode7World.AddObject. After world coordinates are set, you can use Mode7World.UpdateObjects to transform it's coordinates and get updated values in it's Screen prefixed properties.

Mode7Object.SetPosition
Code: ags
void Mode7Object.SetPosition(float x, float y, float z)

A helper function to setting the Object world position in a single line.

Mode7Object.Draw
Code: ags
void Mode7Object.Draw(DrawingSurface* ds)

Draw the object in a DrawingSurface as it would look in a screen.

Mode7Object.X
Code: ags
float Mode7Object.X

Object World X Position on the plane.

Mode7Object.Y
Code: ags
float Mode7Object.Y

Object World Y Position, perpendicular to plane.

Mode7Object.Z
Code: ags
float Mode7Object.Z

Object World Z Position, orthogonal to X position.

Mode7Object.Factor
Code: ags
float Mode7Object.Factor

Object Scaling factor to it's graphics.

Mode7Object.Angle
Code: ags
float Mode7Object.Angle

Object angle, parallel to plane, not used for rendering.

Mode7Object.Graphic
Code: ags
int Mode7Object.Graphic

Object sprite slot, it's width and height is used to calculate the screen coordinates.

Mode7Object.Visible
Code: ags
bool Mode7Object.Visible

Object visibility.

Mode7Object.ScreenX
Code: ags
int Mode7Object.ScreenX

On-Screen Object X position when drawing, if visible. It's regular top, left coordinates, similar to GUI, assumes a Graphic is set.

Mode7Object.ScreenY
Code: ags
int Mode7Object.ScreenY

On-Screen Object Y position when drawing, if visible. It's regular top, left coordinates, similar to GUI, assumes a Graphic is set.

Mode7Object.ScreenWidth
Code: ags
int Mode7Object.ScreenWidth

On-Screen Object Width when drawing, if visible. It's adjusted by the sprite used in Graphic, projection and scaling factor.

Mode7Object.ScreenHeight
Code: ags
int Mode7Object.ScreenHeight

On-Screen Object Height when drawing, if visible. It's adjusted by the sprite used in Graphic, projection and scaling factor.

Mode7Object.ScreenVisible
Code: ags
bool Mode7Object.ScreenVisible

True if object should be drawn on screen. Gets set to false if object is culled when projecting.

Mode7Object.ScreenZOrder
Code: ags
int Mode7Object.ScreenZOrder

ZOrder of the object when drawing on screen, smaller numbers are below, bigger numbers are on top.
[close]

This is just a quick initial release, I plan to update this soon with a better demo and polish the API and other stuff!


  • v0.1.0 - initial release.
  • v0.2.0 - added ResetGround, CameraAngleX, CameraAngleY to Mode7, added Visible to Mode7Object, added AddExternalObject and DrawWorld2D to Mode7World, change GetAngleObjectAndCamera api.
  • v0.3.0 - added support for using Overlays for rendering mode7 objects using Mode7World.DrawObjectsOverlay, and also to skip sorting for overlay based drawing.
#135
Android Build in the Editor


⚠️⚠️This feature is merged and available in 3.6.0 releases⚠️⚠️


running on your computer may have visual differences ... (I am too lazy to update this gif)

Please test and report. Here's the step by step:


  • Install Android Studio, the latest version available
  • Note down the directories it is installed (probably C:\Program Files\Android\Android Studio) and where it installed the Android SDK (probably C:\Users\YOURUSERNAME\AppData\Local\Android\Sdk)
  • open AGS Editor and either create a new game or load a copy of a game you want to test. Don't use your actual game files with this editor since it will add information to your project settings that are not compatible with other 3.6.X versions.
  • On Editor preferences, Set JAVA_HOME to where your JDK is installed, if you have Android Studio, it' s probably C:\Program Files\Android\Android Studio\jre
  • On Editor Preferences, Set ANDROID_HOME to where the Android SDK was installed, it probably is C:\Users\YOURUSERNAME\AppData\Local\Android\Sdk
  • Still on Editor Preferences, set your Android Keystore information. If you don't have a keystore, use the Generate Keystore button. On the keystore generation screen, everything under Certificate is optional.
  • Go in general settings, find the Android entry there and adjust as needed(aab, APK, ...), then on build, select Android among the platforms
  • Hit Build Exe in the Editor and wait things to happen. This will take a lot of time on the first time since it needs to install a lot of things, it should be much faster afterwards

Icons!

At your project root, create a directory named icons with a directory named android inside. Then you need to add the following files ( template here):


  • icons/android/mipmap-mdpi/ic_launcher.png 48x48 pixels, RGBA png icon
  • icons/android/mipmap-hdpi/ic_launcher.png 72x72 pixels, RGBA png icon
  • icons/android/mipmap-xhdpi/ic_launcher.png 96x96 pixels, RGBA png icon
  • icons/android/mipmap-xxhdpi/ic_launcher.png 144x144 pixels, RGBA png icon
  • icons/android/mipmap-xxxhdpi/ic_launcher.png 192x192 pixels, RGBA png icon

If you want to support round icons, additionally create ic_launcher_round.png files in the same directories. If you need help creating those files, try this online AndroidAssetStudio.
FAQ






  • Q: I am missing the Android SDK!
    A: after installing Android Studio, load Android Studio and click in the top menu in Tools, and then in SDK Manager, at top, hit Edit



    In the next screen, set the adequate location and hit next.


    You can hit next here, just note down that the SDK Folder here is what is set as ANDROID_HOME in the Editor preferences, and JDK Location is the JAVA_HOME on the Editor preferences. <<<<


    And then it comes one thing you have to do, if you want to use it, you need to accept the licenses! There are two licenses you need to accept in this screen, so scroll down and hit accept, switch to the next one, scroll down again, and hit accept again.


    We are going to be using SDK 29 in here for now, so if something complains about it, make sure to comeback to the SDK managed and find and click in Android 10 (Q), API Level 29 and then proceed to installing it. If you don't, AGS Editor may try to download and install it anyway if needed when building your app for the first time, after ANDROID_HOME and JAVA_HOME are set!






  • Q: I installed Android Studio using JetBrains Toolbox! But I can't find it... What do I set in ANDROID_HOME ?
    A: OK, if you did this way, the toolbox entry of Android Studio has an entry to show it in file explorer. If you can't find it, it usually uses a directory similar to below:
    C:\Users\MYUSERNAME\AppData\Local\JetBrains\Toolbox\apps\AndroidStudio\ch-0\211.A_TON_OF_NUMBERS\jre





  • Q: How do I set the screen orientation or rotate it?
    A: Screen Rotation has to be configured in Default Setup, currently there are three possible options: Unlocked (player may rotate device screen freely if he unlocks it), Portrait (game can only be vertically oriented) and Landscape (game screen is horizontally oriented). Default is Unlocked.






  • Q: Why is my app name app-mygame-release.apk? Can I change this name?
    A: You did not change the property Android App ID in General Settings. Set it to something like com.incredibleproductions.indianagame and you will get app-indianagame-release.apk.






  • Q: I got a "validateSigningRelease FAILED" message and my build did not work...
    A: It's possible that the keystore was not set, if that is the case go into Preferences, and on Android tab set the correct keystore information. If you don't have a Keystore, please generate one using the Generate Keystore button, and click OK after successfully generating one.







  • Q: Build is a bit slow, can I do anything to speed it up?
    A: In Editor Preferences, under Advanced tab, in Android, select Use Gradle Daemon and mark it true.




There is still room for improvement, but this should do the general things...

Also when possible please try these experimental versions of editors that appears in this board, the feedback given can help make ags better. After the feature is already in, some things get a lot harder to change.

Spoiler

Additionally, I recently started thinking about build matrixes in AGS, if someone has any design idea for this. It looks something like this (this is a different editor than the above):

[close]
#136
🚨!!Just to test log, backup things, if you are playing with this!!🚨

⚠️>Download: AGS-3.6.0.35-Beta15_DebugLog.zip<⚠️

So, we currently have logging in the engine, and it's very nice, it has all sorts of options. You can use System.Log function for it. But currently you have to either use AGS through command line or configure it to log to a file.

See log levels at the end of page here: https://adventuregamestudio.github.io/ags-manual/StandardEnums.html#loglevel

Well, I want to add a panel so when you run it through the Editor debugger you can pick up your log messages right there. You may have to enable it, in the above build, by clicking on Help-> Show Debug Log



Anyway, would this be useful to others? Is there anything in particular you want to use in such feature?
#137
Engine Development / AGS engine Web port
Wed 26/05/2021 02:45:17
the web port is now available along AGS 3.6.X releases!

>>any game launcher<<
    single game example
it's AGS, so Alt+Enter will get you in/out of Fullscreen

please report bugs here or in AGS GitHub issue tracker! If you are unsure if it's a bug, ask about problems here in the thread!!!

New: CW did an amazing video and sound refactor and after it, it was possible to get video working on the web port! Download this game to test in the above launcher link!

Old packaging instructions are not needed anymore since it should now be possible to build directly from the Editor by simpling checking the appropriate box in General Settings and selecting the Web as a build target.

Packaging a Game

  • In the AGS Editor, go to General Settings, and under Compiler, there is the Build target platforms entry, select Web!
  • Click Build EXE (f7)
  • in the Compiled/Web/, you can test running locally a web server in the directory (python3 -m http.server or any other tiny local server you have) and opening the address in the browser.
  • Remember that if you use anything beyond left click your game won't be mobile friendly for now.
  • TEST BEFORE SHIPPING
  • zip the contents of the Compiled/Web/ dir and upload it in either itch.io or gamejolt so people can play your web port.

Spoiler

Web packages now are available with AGS Releases in GitHub, with a name similar to ags_3.6.0.17_web.tar.gz, with versions changing accordingly. But they are not needed anymore if you are using AGS Editor since you can now build directly from there!
The old instructions are left here for historical purposes.

  • Download the zip above and extract the files to an empty folder
  • Copy your game files to the same directory. If possible, use the .ags version from Compiled/Data
  • Edit the file my_game_files.js overwriting the array with your game files.
  • Edit the title tag on index.html with your game name.
  • Optionally, test running locally a web server in the directory (python3 -m http.server) and opening the address in the browser.
  • Remember that if you use anything beyond left click your game won't be mobile friendly for now.
  • TEST BEFORE SHIPPING
[close]

Notes: the port won't work in any big ags game (like a game beyond 300MB (unless you are like in charge of the navigator using electron or phonegap or something...)).

itch.io
Spoiler
Itch provides here a small guide on html5 games that might be relevant. Config is something like this (note, the resolution has to match your game resolution).

Only mark web-friendly if your game is Left mouse click only (for now everything else will be difficult to support in the touch only device). You can also use itch's full-screen button for now.
[close]
Gamejolt
Spoiler
is a bit simpler, after uploading the zip file
[close]

If you want to build this binary from source...

Known issues

  • Currently the webport kidnaps all keyboard keys, but I could do focus handling (on HTML canvas), if someone has a need for this tell me about and we can test your use case.
  • If you want persistent save files, ask me about it here in the thread, they are technically possible
  • Won't render correctly LucasFan-Font.ttf
  • Can't play MIDI, if you intend to build for web, avoid it. Use ogg files always.
  • Browser interprets ESC key as exiting of Fullscreen (not sure why yet since it's not a default behavior)
  • Last builds are hitting a Chrome bug in Mobile, the way to avoid is using an old Emscripten when building, but this has it's own issues. THE BUG HAS BEEN FIXED in Chromium upstream, and it will be running flawless in Chrome 100, you can test now by installing Chrome Canary. Chrome 100 will be stable after March 29 , 2022!

Extra Information

  • System.OperatingSystem has an enum for the Web system, if you want to account for differences in your game at runtime
  • leave sprite compression on for your game, it has no perceptible perfomance impact.
  • Port idea originated from the previous topic (here).

If you know JS or CSS, I really need help making the HTML and file loading prettier. Below is current loading:

#138
ImGi version 0.4.2


Get Latest Release imgi.scm | GitHub Repo | Demo Windows | Demo Linux | Download project .zip

AGS Script Module for Immediate Gui, uses script Overlays to render the interface.

This is beta quality, I predict Overlays will enter into fashion in one of the next seasons so this module is being written for when the time comes.  8-)



Usage
Spoiler

Code: ags
function repeatedly_execute() // In Room Script, use room_RepExec()
{
  ImGi.Begin();

  if(ImGi.BeginWindow("Hello World", 32, 32, 130, 60, eImGi_Opt_AlignCenter | eImGi_Opt_NoClose))
  {
    // makes following rows to have two columns of width 60 and 70, and let height be default
    ImGi.LayoutRow2(60, 70);

    ImGi.Label("A Label:");
    if(ImGi.Button("A Button!"))
    {
      player.Say("You clicked the button!");
    }

    ImGi.EndWindow();
  }

  ImGi.End();
}
[close]

Script API
Spoiler

ImGi entire API uses static functions and attributes

Basic

ImGi.Begin
Code: ags
void ImGi.Begin()

Call only once per frame and make sure to call End() after.

ImGi.End
Code: ags
void ImGi.End()

Call only once per frame, after Begin() is called.



Layout System

ImGi uses a row based layout system. Each row can have a number of columns (up to 16 columns), each with it's own width, and also a height.
Controls then will be placed in this cell. If you don't change how rows are, it's assumed to keep same LayoutRow.

If you don't specify a height, it will use your style size and padding. Places elements relative to the bottom. Controls may make height bigger than what you set.
If your column width is 0, it will your style size and padding. A negative width will try to place that element at that distance relative from the rights.

Some special Controls can force the width of it's specific column, ignoring the positive values you set, this can be useful if you don't know before hand their sizes.

ImGi.LayoutRow1
ImGi.LayoutRow2
ImGi.LayoutRow3
ImGi.LayoutRow4
Code: ags
void ImGi.LayoutRow1(int width, int height = 0)
void ImGi.LayoutRow2(int w1, int w2, int height = 0)
void ImGi.LayoutRow3(int w1, int w2, int w3, int height = 0)
void ImGi.LayoutRow4(int w1, int w2, int w3, int w4, int height = 0)

Functions to configure the next LayoutRow to use, from a single column (LayoutRow1) up to four columns (LayoutRow4). Use these if you know you want either of these number of columns.
You can optionally specify a height.

ImGi.LayoutRow
Code: ags
void ImGi.LayoutRow(int count, int widths[], int height = 0)

Pass an array of widths with count elements to configure the columns in a row. You can optionally specify a height.

This is useful if you are reading an array of things or have 5 or more columns in your layout. The maximum number of widths is 16.
Code: ags
int row[];

row = new int[2];
row[0] = 60; // set a predefined column width size per element in row
row[1] = 70; // this is the width of other column
ImGi.LayoutRow(2 /*n columns*/, row); // rows after this line have such column config


ImGi.LayoutBeginColumn
ImGi.LayoutEndColumn
Code: ags
void ImGi.LayoutBeginColumn()
void ImGi.LayoutEndColumn()

Allows subdividing a cell in a row in more rows and columns. You start the column with ImGi.LayoutBeginColumn(), and it's void, so ALWAYS call LayoutEndColumn() after (you don't check it's return value because it's void!).




Window

A window can be created by a BeginWindow and if this is successful (returns any non false value), it has to call EndWindow to specify where it logically ends.
All controls must exist within a window. An example of normal usage is below:

Code: ags
if(ImGi.BeginWindow("My First Window!", 32, 32, 130, 60))
{
  ImGi.Text("Hi!"); // your controls are here ...
  ImGi.EndWindow();
}



ImGi.BeginWindow
Code: ags
ImGi_Res ImGi.BeginWindow(String title, int x, int y, int width, int height, ImGi_Opt opt = 0)

Creates a window, make sure to call a matching EndWindow() if this method return is not false.

ImGi.EndWindow
Code: ags
void ImGi.EndWindow()

Has to be called each time a BeginWindow is successful once all elements inside the window are listed

ImGi.OpenWindow
Code: ags
void ImGi.OpenWindow(String title)

If a window of matching title is closed, it opens again.

ImGi.Close
Code: ags
void ImGi.Close()

Closes what is the current scope (Window, Popup, ...). Don't call it outside of a Window, a Popup, ...

ImGi.BeginPopup
ImGi.EndPopup
ImGi.OpenPopup
Code: ags
ImGi_Res ImGi.BeginPopup(String title)
void ImGi.EndPopup()
void OpenPopup(String name)


Popups are like windows, but you can't move or resize them, and they have no headers. They open where the mouse is when calling OpenPopup by default.
Popups open on top of everything, and they close if you click outside of them. Clicks outside of the popup that hit no window will be forwarded to the rest of your game to handle.

ImGi.BeginPanel
ImGi.EndPanel
Code: ags
ImGi_Res ImGi.BeginPanel(String name, ImGi_Opt opt = 0)
void ImGi.EndPanel()


If you need a scrollable area inside a window that is not the window itself, you can use panels! A panel has to be inside of a window, it will use the LayoutRow cell size for it's size. If it returns successful, you have to call EndPanel() after.
Code: ags
if(ImGi.BeginPanel("Pan")){
  ImGi.Text("Hi panel!"); // your controls are here ...
  ImGi.EndPanel();
}





Controls

Controls are things you can place inside a window. Controls cannot exist outside of windows.

Every controls takes a string as label, this string can't be empty and can't match the label of other control. The exception is if the control can take an icon or graphic, then if you pass null as the string and the control has an icon, it will use the icon if possible. Each window or similar will be a new scope, used to compose this identification, so two different windows can have controls with matching labels.

ImGi comes with a limited number of icons, whenever you can provide an icon as a parameter, you can alternatively pass a sprite number, and in this case it will use that sprite instead of the icon. The default icons use negative numbers, so they don't conflict with your sprite ID.

ImGi.Empty
Code: ags
void ImGi.Empty()

This control does nothing and is invisible. Use it when you don't want to place anything in cell to advance the layout.

ImGi.Label
Code: ags
void ImGi.Label(String label)



This control is a Label containing the specified text. It has no interaction.

ImGi.Text
Code: ags
void ImGi.Text(String text)



This control is a Multiline Label for visualization only. It has no interaction.

ImGi.TextBox
Code: ags
String ImGi.TextBox(String label, String buf, int bufsz, ImGi_Result* res = 0, ImGi_Opt opt = 0)

This control is an editable TextBox. Click on it to give focus and enter the text input with the keyboard. Enter exits focus.

The character limit is defined in bufsz. This function will return the buf String modified, just assign it to the same String so it's content can be updated.

ImGi.Button
Code: ags
ImGi_Res ImGi.Button(String label, ImGi_Icon icon = 0, ImGi_Opt opt = eImGi_Opt_AlignCenter)



This control is a Button. When clicked, it will return a value different than false.

ImGi.ButtonImage
Code: ags
ImGi_Res ImGi.ButtonImage(String label, int graphic_normal, int graphic_over, int graphic_pressed, ImGi_Opt opt = 0)


Pass a sprite for the Button normal state, one for when mouse is over, and a graphic for when it's clicked. You can set label null if it's the only button in the window with same graphics.

ImGi.CheckBox
Code: ags
ImGi_Res ImGi.CheckBox(String label, CheckBoxState* chkst, ImGi_Icon icon = eImGi_Icon_Check)



This control is a CheckBox. It doesn't store state, so make sure to pass it's state. You optionally pass a different icon to it.

ImGi.Number
ImGi.NumberI
Code: ags
ImGi_Res ImGi.Number(String label, ImGi_Real* value, float step = 0, String format = 0, ImGi_Opt opt = 0)
ImGi_Res ImGi.NumberI(String label, ImGi_Int* value, int step = 0, String format = 0, ImGi_Opt opt = 0)



This control shows a Number, set step to allow quick mouse drag adjustments. Holding shift and clicking it allows entering input with the keyboard.

You can pass a format string similar to the one used with String.Format to specify how the number should be rendered. It's a float, so make sure to use either "%f" or "%g".

NumberI is the same control but for integer (int) numbers, it's not the same control just wrapped, so it's format string default is "%d".

ImGi.Slider
ImGi.SliderI
Code: ags
ImGi_Res ImGi.Slider(String label, ImGi_Real* value, float low, float high, float step = 0,  String format = 0, ImGi_Opt opt = 0)
ImGi_Res ImGi.SliderI(String label, ImGi_Int* value, int low, int high, int step = 0, String format = 0, ImGi_Opt opt = 0)



This control is a Slider. You can adjust it manually with the mouse or you can hold shift and click to specify a value with the keyboard.

You can pass a format string similar to the one used with String.Format to specify how the number should be rendered. It's a float, so make sure to use either "%f" or "%g".

SliderI is the same control but for integer (int) numbers, it's not the same control just wrapped, so it's format string default is "%d".



Options

Some controls and other elements can take options. Below is the list of available options. If you wish to pass two or more options, you can combine them with the bitfield or operator |

Code: ags
ImGi.Button("centered text and non-interactive",0, eImGi_Opt_AlignCenter | eImGi_Opt_NoInteract)



eImGi_Opt_AlignCenterThe header of a window or the control will have text aligned to center.
eImGi_Opt_AlignRightThe header of a window or the control will have text aligned to right.
eImGi_Opt_NoInteractDisables interaction with the control.
eImGi_Opt_NoFrameIf the control or window has any frame, it's not drawn.
eImGi_Opt_NoResizeYou can't resize the window by click-dragging it's bottom right corner.
eImGi_Opt_NoScrollWindow has no scrollbars.
eImGi_Opt_NoCloseWindow has no close button.
eImGi_Opt_NoTitleWindow has to title bar.
eImGi_Opt_HoldFocusControls with this option will require clicking on a different control to remove focus. Default of some controls.
eImGi_Opt_AutoSizeMakes the window resize to fit content.
eImGi_Opt_PopUpCloses the container when clicking out of it. This is used by default in Popus.
eImGi_Opt_ClosedMakes the container start closed by default.
eImGi_Opt_ExpandedThese are for tree elements, which are not implemented yet.




Utilities

ImGi.SetFocusLastControl
Code: ags
void ImGi.SetFocusLastControl()

Places the focus on what is the last control. Some controls behave differently when focused - like Number and TextBox. Focus is only reggarding input, but won't scroll or move things at center.




Style and Design customization

ImGi.Style
Code: ags
ImGi_Style* ImGi.Style

Holds the Current Style for ImGi.

[close]

License

This code is licensed with MIT LICENSE. The code on this module is based on rxi's Microui, which is also MIT licensed, referenced in the license, this port though has many changes - lots of new bugs too.
#139
rellax version 0.4.0

Get Latest Release rellax.scm | GitHub Repo | Demo Windows | Demo Linux | Download project .zip

Rellax while the camera tracks with cool parallax



This module uses the camera and Viewport API from Adventure Game Studio 3.5.0.

Demo game uses keyboard arrows control, up arrow jumps. WASD should also work.


Usage
Before starting, you must create the following Custom Properties in AGS Editor, for usage with Objects.
Just click on Properties [...] and on the Edit Custom Properties screen, click on Edit Schema ... button, and add the two properties below:

PxPos:
  • Name: PxPos
  • Description: Object's horizontal parallax
  • Type: Number
  • Default Value: 0

PyPos:
  • Name: PyPos
  • Description: Object's vertical parallax
  • Type: Number
  • Default Value: 0

The number defined on Px or Py will be divided by 100 and used to increase the scrolling.
An object with Px and Py 0 is scrolled normally, an object with Px and Py 100 will be fixed on the screen despite camera movement.
Objects with negative Px and Py are usually at the front, and positive values are usually at the back.


Script API

static attribute Character* TargetCharacter
The character being tracked by the Game.Camera.

static attribute bool EnableParallax
Gets/sets whether Parallax is on or off.

static attribute bool EnableSmoothCam
Gets/sets whether Smooth Camera tracking is on or off.

static attribute bool AutomaticallySetupOnRoomLoad
Gets/sets whether to automatically setup on room load. It defaults to yes (true).
Leave as this unless you really need it.

static void SetupRoomManually()
You should not call this, unless AutomaticallySetupOnRoomLoad is false.
Then call this at the end of your room_Load, after you are done setting things up.

static attribute bool AdjustCameraOnRoomLoad
Gets/sets whether to instantly adjust camera to target on room before fade in, when Smooth Camera is on. Default is true.

static attribute RellaxTweenEasingType EasingType
gets/sets the camera tween type to use when the character is stopped.

static attribute float TweenDuration
gets/sets the camera tween duration once the character is stopped.

static attribute int CameraOffsetX
Gets/sets the camera horizontal offset. It's applied without smoothing.

static attribute int CameraOffsetY
Gets/sets the camera vertical offset. It's applied without smoothing.

static attribute int CameraLookAheadX
Gets/sets the camera horizontal lookahead offset. This is an additional offset that is added in the direction the target character is facing (only 4 direction support now).

static attribute int CameraLookAheadY
Gets/sets the camera vertical lookahead offset. This is an additional offset that is added in the direction the target character is facing (only 4 direction support now).

static attribute int CameraLerpFactorX
Gets/sets the factor the camera should use when interpolating in the X axis.

static attribute int CameraLerpFactorY
Gets/sets the factor the camera should use when interpolating in the Y axis.

static attribute int CameraWindowWidth
Gets/sets the camera window width that is centered on the target lookahead point, when the target is outside of the window, the camera moves to keep it inside.

static attribute int CameraWindowHeight
Gets/sets the camera window height that is centered on the target lookahead point, when the target is outside of the window, the camera moves to keep it inside.


License
This module is created by eri0o is provided with MIT License, see LICENSE for more details.
The code on this module is based on the code of Smooth Scrolling + Parallax Module from Alasdair Beckett, which bases on code from Steve McCrea.
It uses easing code based on Edmundo Ruiz and Robert Penner's, works, which are MIT and BSD licensed, respectively (and included in the module script).
The demo game uses CC0 (Public Domain) art provided by jetrel.
#140
Controlz  0.3.1

controlz.scm | controlz_demo_windows.zip | controlz_demo_linux.tar.gz | GitHub repo

Move your character with keyboard or joystick controlz for Adventure Game Studio.



This code was originally made by Dualnames for Strangeland and I eri0o got his permission to open source and wrapped in this function to be easier for consumption.

Usage example

Call it on repeatedly execute, in your Global Script or in the script that you deal with player input, passing a character and some way to check for directional keys, once for each direction (down, left, right, up).

In the example below, WASD will be used to control the player character, and arrow keys will be used to control a second character named cEgo2. Since 0.3.0, solid characters collide with solid characters.

Code: ags
// called on every game cycle, except when the game is blocked
function repeatedly_execute() 
{
  Controlz(player, 
    IsKeyPressed(eKeyDownArrow),
    IsKeyPressed(eKeyLeftArrow), 
    IsKeyPressed(eKeyRightArrow),
    IsKeyPressed(eKeyUpArrow));

  Controlz(cEgo2, 
    IsKeyPressed(eKeyS),  IsKeyPressed(eKeyA), 
    IsKeyPressed(eKeyD),  IsKeyPressed(eKeyW));
}

script API

Controlz only has a single function

Controlz(Character* c, bool down, bool left, bool right, bool up)

Call it on your repeatedly execute or repeatedly execute always, passing a character and which keys are pressed at that time. If you need to control more characters, just call Controlz again, passing the new character and the buttons that are mapped to move it.

You can check for multiple keys or inputs too.

Code: ags
function repeatedly_execute() 
{
  Controlz(player, 
    IsKeyPressed(eKeyDownArrow) || IsKeyPressed(eKeyS),
    IsKeyPressed(eKeyLeftArrow) || IsKeyPressed(eKeyA), 
    IsKeyPressed(eKeyRightArrow) || IsKeyPressed(eKeyD),
    IsKeyPressed(eKeyUpArrow) || IsKeyPressed(eKeyW));
}

License

This code is licensed with MIT LICENSE.
SMF spam blocked by CleanTalk