Dificulty with Hotspots in an Interaction Detector [Solved]

Started by eri0o, Sun 23/07/2017 23:31:02

Previous topic - Next topic

eri0o

Hello,

I am trying to create an InteractionDetector for my game, Objects and Characters are easy to get the X and Y positions, but Hotspots are trickier.

So I decided to go pixel by pixel on the screen, check which hotspot that pixel belongs to, and note down the minimal and maximum x and y positions, the idea is to later do:
y=miny+(maxy-miny)/2 .

But it's not working! I know the error is in the generate_hotspot_xy function, but I have no idea what's wrong.

Below is the full code, if you want to test it, just replace the font enumerator for your game. I think the rest is not dependent of game code.


Fullcode: InteractionDetector.asc
Spoiler

Code: ags

// new module script
DynamicSprite* interactionSprite;
DrawingSurface * interactionSurface;
Overlay* interactionOverlay ;
bool interactionShown;

int hotspot_minx[MAX_HOTSPOTS_PER_ROOM];
int hotspot_maxx[MAX_HOTSPOTS_PER_ROOM];
int hotspot_miny[MAX_HOTSPOTS_PER_ROOM];
int hotspot_maxy[MAX_HOTSPOTS_PER_ROOM];
int hotspot_x[MAX_HOTSPOTS_PER_ROOM];
int hotspot_y[MAX_HOTSPOTS_PER_ROOM];

//this is the function used to draw each label
function draw_label(String text, int textx,  int texty){
  int textcolor = Game.GetColorFromRGB(216,216, 216);
  if(textx+GetTextWidth(text, eFontSmallFont)+4>Room.Width){
    textx=Room.Width-GetTextWidth(text, eFontSmallFont)-4;
  }
  interactionSurface.DrawingColor = textcolor;
  interactionSurface.DrawString(textx, texty, eFontSmallFontOutlined, text);
}

//this function shows the interaction detector when pressed
static void InteractionDetector::show() {  
  int obj_count = Room.ObjectCount;
  int cha_count = Game.CharacterCount;
  int i;
  int textx,  texty;
  String text;
  interactionShown = true;
  interactionSprite = DynamicSprite.Create(Room.Width, Room.Height);
  interactionSurface = interactionSprite.GetDrawingSurface();
  Character *c;

  
  i=0;
  while(i<obj_count){
    if(object[i].Visible && object[i].Clickable){
      text = object[i].Name;
      textx=object[i].X;
      texty=object[i].Y;
      draw_label(text, textx, texty);
    }
    i++;
  }
  
 
  i=0;
  while(i<MAX_HOTSPOTS_PER_ROOM){
    text = hotspot[i].Name;
    if(hotspot[i].Enabled){
      if(text.Length > 1 && text.IndexOf("Hotspot") == -1){
        textx=hotspot_x[i];
        texty=hotspot_y[i];
        
        draw_label(text, textx, texty);
      }      
    }
    i++;
  }
  
  
  i=0;
  while (i < cha_count) {
    c = character[i];
    if (c.Room == player.Room && c.Clickable && c.ID != player.ID) {
      ViewFrame *cviewframe = Game.GetViewFrame(c.View, c.Loop, c.Frame);
      textx = c.x;
      texty = c.y - Game.SpriteHeight[cviewframe.Graphic]*c.Scaling/2*100;
      text = c.Name;
      if(text.Length>2){
        draw_label(text, textx, texty);
      }
    }
    i++;
  }
  
  interactionOverlay= Overlay.CreateGraphical(0, 0, interactionSprite.Graphic, true);
  player.Say("testx = %d, texty = %d",hotspot_x[2], hotspot_y[2]);
}

//hides the Interaction Detector Overlay
static void InteractionDetector::hide() { 
  interactionOverlay.Remove();
  interactionSurface.Release();
  interactionSprite.Delete();
  interactionShown = false;
}

//external function to check if the Interaction Detector Overlay is already shown
static bool InteractionDetector::isShown() {  
  return interactionShown;
}

//this allows toglin on and off the Interaction Detector Overlay
static void InteractionDetector::toggle() {  
  if(interactionShown){
    InteractionDetector.hide();
  } else {
    InteractionDetector.show();
  }
}

//this function calculates each hotspot position on the screen
function generate_hotspot_xy(){
  int i;
  Hotspot *h;
  
  i=0;
  while(i<MAX_HOTSPOTS_PER_ROOM){
    hotspot_minx[i]=999;
    hotspot_miny[i]=999;
    hotspot_maxx[i]=0;
    hotspot_maxy[i]=0;
    hotspot_x[i]=-1;
    hotspot_y[i]=-1;
    i++;
  }
  
  int x=0;
  int y=0;
  int step=3;
  int h_id;
  
  //we will walk the screen in steps
  //get the hotspot at the x,y point
  //and note down the smaller and bigger
  //x,y position for each hotspot
  while(y<System.ViewportHeight - 1){
    while(x<System.ViewportWidth - 1){

      h = Hotspot.GetAtScreenXY(x, y);
      h_id = h.ID;
      if(h_id>0){
        if(x < hotspot_minx[h_id] ){
          hotspot_minx[h_id] = x;
        } 
        
        if(y < hotspot_miny[h_id]){
          hotspot_miny[h_id] = y;
        } 
        
        if(x > hotspot_maxx[h_id]){
          hotspot_maxx[h_id] = x;
        } 
        
        if(y > hotspot_maxy[h_id]){
          hotspot_maxy[h_id] = y;
        } 
      }
      
      
      x+=step;
    }
    y+=step;
  }
  
  //using the previously obtained max and min x and y values
  //we calculate the center of the hotspot
  i=0;
  while(i<MAX_HOTSPOTS_PER_ROOM){
    hotspot_x[i]=hotspot_minx[i]+(hotspot_maxx[i]-hotspot_minx[i])/2;
    hotspot_y[i]=hotspot_miny[i]+(hotspot_maxy[i]-hotspot_miny[i])/2;
    i++;
  } 
}


//everytime a room is loaded, we must recalculate it's hotspot position.
function on_event (EventType event, int data)
{
  if (event == eEventEnterRoomBeforeFadein){
    generate_hotspot_xy();
  } 
}


[close]

Fullcode InteractionDetector.ash
Spoiler

Code: ags

// new module header
#define MAX_HOTSPOTS_PER_ROOM 49

struct InteractionDetector
{
  import static void show();
  import static void hide();  
  import static void toggle();  
  import static bool isShown();  
};

[close]

EDIT:

Fixed Code Below! Thanks Khris!

InteractionDetector.asc
Spoiler
Code: ags

// new module script
DynamicSprite* interactionSprite;
DrawingSurface * interactionSurface;
Overlay* interactionOverlay ;
bool interactionShown;

int hotspot_minx[MAX_HOTSPOTS_PER_ROOM];
int hotspot_maxx[MAX_HOTSPOTS_PER_ROOM];
int hotspot_miny[MAX_HOTSPOTS_PER_ROOM];
int hotspot_maxy[MAX_HOTSPOTS_PER_ROOM];
int hotspot_x[MAX_HOTSPOTS_PER_ROOM];
int hotspot_y[MAX_HOTSPOTS_PER_ROOM];

//this is the function used to draw each label
function draw_label(String text, int textx,  int texty){
  int textcolor = Game.GetColorFromRGB(216,216, 216);
  if(textx+GetTextWidth(text, eFontSmallFont)+4>Room.Width){
    textx=Room.Width-GetTextWidth(text, eFontSmallFont)-4;
  }
  interactionSurface.DrawingColor = textcolor;
  interactionSurface.DrawString(textx, texty, eFontSmallFontOutlined, text);
}

//this function shows the interaction detector when pressed
static void InteractionDetector::show() {  
  int obj_count = Room.ObjectCount;
  int cha_count = Game.CharacterCount;
  int i;
  int textx,  texty;
  String text;
  interactionShown = true;
  interactionSprite = DynamicSprite.Create(Room.Width, Room.Height);
  interactionSurface = interactionSprite.GetDrawingSurface();
  Character *c;

  
  i=0;
  while(i<obj_count){
    if(object[i].Visible && object[i].Clickable){
      text = object[i].Name;
      textx=object[i].X;
      texty=object[i].Y;
      draw_label(text, textx, texty);
    }
    i++;
  }
  
 
  i=0;
  while(i<MAX_HOTSPOTS_PER_ROOM){
    text = hotspot[i].Name;
    if(hotspot[i].Enabled){
      if(text.Length > 1 && text.IndexOf("Hotspot") == -1){
        textx=hotspot_x[i];
        texty=hotspot_y[i];
        
        draw_label(text, textx, texty);
      }      
    }
    i++;
  }
  
  
  i=0;
  while (i < cha_count) {
    c = character[i];
    if (c.Room == player.Room && c.Clickable && c.ID != player.ID) {
      ViewFrame *cviewframe = Game.GetViewFrame(c.View, c.Loop, c.Frame);
      textx = c.x;
      texty = c.y - Game.SpriteHeight[cviewframe.Graphic]*c.Scaling/2*100;
      text = c.Name;
      if(text.Length>2){
        draw_label(text, textx, texty);
      }
    }
    i++;
  }
  
  interactionSurface.Release();
  interactionOverlay= Overlay.CreateGraphical(0, 0, interactionSprite.Graphic, true);
}

//hides the Interaction Detector Overlay
static void InteractionDetector::hide() { 
  interactionOverlay.Remove();
  interactionSprite.Delete();
  interactionShown = false;
}

//external function to check if the Interaction Detector Overlay is already shown
static bool InteractionDetector::isShown() {  
  return interactionShown;
}

//this allows toglin on and off the Interaction Detector Overlay
static void InteractionDetector::toggle() {  
  if(interactionShown){
    InteractionDetector.hide();
  } else {
    InteractionDetector.show();
  }
}

//this function calculates each hotspot position on the screen
function generate_hotspot_xy(){
  int i;
  Hotspot *h;
  
  i=0;
  while(i<MAX_HOTSPOTS_PER_ROOM){
    hotspot_minx[i]=999;
    hotspot_miny[i]=999;
    hotspot_maxx[i]=0;
    hotspot_maxy[i]=0;
    hotspot_x[i]=-1;
    hotspot_y[i]=-1;
    i++;
  }
  
  int x=0;
  int y=0;
  int step=3;
  int h_id;
  
  //we will walk the screen in steps
  //get the hotspot at the x,y point
  //and note down the smaller and bigger
  //x,y position for each hotspot
  while(y<System.ViewportHeight - 1){
    x=0;
    while(x<System.ViewportWidth - 1){

      h = Hotspot.GetAtScreenXY(x, y);
      h_id = h.ID;
      if(h_id>0){
        if(x < hotspot_minx[h_id] ){
          hotspot_minx[h_id] = x;
        } 
        
        if(y < hotspot_miny[h_id]){
          hotspot_miny[h_id] = y;
        } 
        
        if(x > hotspot_maxx[h_id]){
          hotspot_maxx[h_id] = x;
        } 
        
        if(y > hotspot_maxy[h_id]){
          hotspot_maxy[h_id] = y;
        } 
      }
      
      
      x=x+step;
    }
    y=y+step;
  }
  
  //using the previously obtained max and min x and y values
  //we calculate the center of the hotspot
  i=0;
  while(i<MAX_HOTSPOTS_PER_ROOM){
    hotspot_x[i]=hotspot_minx[i]+(hotspot_maxx[i]-hotspot_minx[i])/2;
    hotspot_y[i]=hotspot_miny[i]+(hotspot_maxy[i]-hotspot_miny[i])/2;
    i++;
  } 
}


//everytime a room is loaded, we must recalculate it's hotspot position.
function on_event (EventType event, int data)
{
  if (event == eEventEnterRoomBeforeFadein){
    generate_hotspot_xy();
  } 
}
[close]


InteractionDetector.ash
Spoiler
Code: ags

// new module header
#define MAX_HOTSPOTS_PER_ROOM 49

struct InteractionDetector
{
  import static void show();
  import static void hide();  
  import static void toggle();  
  import static bool isShown();  
};

[close]


EDIT2:

This is my final version that solves label colision:

InteractionDetector.asc

Spoiler
Code: ags

// new module script
DynamicSprite* interactionSprite;
DrawingSurface * interactionSurface;
Overlay* interactionOverlay ;
bool interactionShown;

int hotspot_minx[MAX_HOTSPOTS_PER_ROOM];
int hotspot_maxx[MAX_HOTSPOTS_PER_ROOM];
int hotspot_miny[MAX_HOTSPOTS_PER_ROOM];
int hotspot_maxy[MAX_HOTSPOTS_PER_ROOM];
int hotspot_x[MAX_HOTSPOTS_PER_ROOM];
int hotspot_y[MAX_HOTSPOTS_PER_ROOM];


int textcolor;

int label_index;
String scheduled_text[MAX_LABELS];
bool scheduled_text_enabled[MAX_LABELS];
int scheduled_text_x[MAX_LABELS];
int scheduled_text_y[MAX_LABELS];

//this is the function used to draw each label
function schedule_drawing(String text, int textx,  int texty){
  scheduled_text[label_index]=text;
  scheduled_text_enabled[label_index]=true;
  scheduled_text_x[label_index] = textx;
  if(textx+GetTextWidth(text, eFontSmallFont)+4>System.ViewportWidth+40){
    scheduled_text_enabled[label_index]=false; 
  }
  
  if(textx+GetTextWidth(text, eFontSmallFont)+4>System.ViewportWidth){
    scheduled_text_x[label_index]=System.ViewportWidth-GetTextWidth(text, eFontSmallFont)-4;
  }
  scheduled_text_y[label_index] = texty;
  label_index++;
}


function draw_label(String text, int textx,  int texty){

  interactionSurface.DrawingColor = textcolor;
  interactionSurface.DrawString(textx, texty, eFontSmallFontOutlined, text);
}

function fix_label_positions(){
  int i;
  int j;
  i=0;
  j=0;
  int textwidth;
  while(i<MAX_LABELS){
    if(scheduled_text_enabled[i]){
      textwidth=GetTextWidth(scheduled_text[i], eFontSmallFont) ;
      j=0;
      while(j<MAX_LABELS){
        if(scheduled_text_enabled[j] && i!=j){
           if(scheduled_text_y[i]<scheduled_text_y[j]+9 && scheduled_text_y[i]>scheduled_text_y[j]-9){
             if(scheduled_text_x[i]<textwidth+scheduled_text_x[j]+8 && scheduled_text_x[i]>scheduled_text_x[j]-textwidth-8){
               scheduled_text_y[i]=scheduled_text_y[i]-6;
               scheduled_text_y[j]=scheduled_text_y[j]+2;
               break;
             }
           }
        }
        j++;
      }
    }
    i++;
  }  
}

function draw_all_labels(){
  textcolor = Game.GetColorFromRGB(216,216, 216);  
  int i;
  fix_label_positions();
  fix_label_positions();
  
  i=0;
  while(i<MAX_LABELS){
    if(scheduled_text_enabled[i]){
      draw_label(scheduled_text[i] ,scheduled_text_x[i], scheduled_text_y[i]);
    }
    i++;
  }
}


//this function calculates each hotspot position on the screen
function generate_hotspot_xy(){
  int i;
  Hotspot *h;
  
  i=0;
  while(i<MAX_HOTSPOTS_PER_ROOM){
    hotspot_minx[i]=999;
    hotspot_miny[i]=999;
    hotspot_maxx[i]=0;
    hotspot_maxy[i]=0;
    hotspot_x[i]=-1;
    hotspot_y[i]=-1;
    i++;
  }
  
  int x=0;
  int y=0;
  int step=3;
  int h_id;
  
  //we will walk the screen in steps
  //get the hotspot at the x,y point
  //and note down the smaller and bigger
  //x,y position for each hotspot
  while(y<System.ViewportHeight - 1){
    x=0;
    while(x<System.ViewportWidth - 1){

      h = Hotspot.GetAtScreenXY(x, y);
      h_id = h.ID;
      if(h_id>0){
        if(x < hotspot_minx[h_id] ){
          hotspot_minx[h_id] = x;
        } 
        
        if(y < hotspot_miny[h_id]){
          hotspot_miny[h_id] = y;
        } 
        
        if(x > hotspot_maxx[h_id]){
          hotspot_maxx[h_id] = x;
        } 
        
        if(y > hotspot_maxy[h_id]){
          hotspot_maxy[h_id] = y;
        } 
      }
      
      
      x=x+step;
    }
    y=y+step;
  }
  
  //using the previously obtained max and min x and y values
  //we calculate the center of the hotspot
  i=0;
  while(i<MAX_HOTSPOTS_PER_ROOM){
    hotspot_x[i]=hotspot_minx[i]+(hotspot_maxx[i]-hotspot_minx[i])/2;
    hotspot_y[i]=hotspot_miny[i]+(hotspot_maxy[i]-hotspot_miny[i])/2;
    i++;
  } 
}

//this function shows the interaction detector when pressed
static void InteractionDetector::show() {  
  int obj_count = Room.ObjectCount;
  int cha_count = Game.CharacterCount;
  int i;
  int textx,  texty;
  String text;
  interactionShown = true;
  interactionSprite = DynamicSprite.Create(System.ViewportWidth, System.ViewportHeight);
  interactionSurface = interactionSprite.GetDrawingSurface();
  Character *c;
  
  label_index=0;
  generate_hotspot_xy();

  i=0;
  while(i<MAX_LABELS){
    scheduled_text_enabled[i] = false;
    i++;
  }
  
  i=0;
  while(i<obj_count){
    if(object[i].Visible && object[i].Clickable){
      text = object[i].Name;
      textx=object[i].X-GetViewportX();
      texty=object[i].Y-GetViewportY();
      schedule_drawing(text, textx, texty);
    }
    i++;
  }
  
 
  i=0;
  while(i<MAX_HOTSPOTS_PER_ROOM){
    text = hotspot[i].Name;
    if(hotspot[i].Enabled){
      if(text.Length > 1 && text.IndexOf("Hotspot") == -1){
        textx=hotspot_x[i];
        texty=hotspot_y[i];
        
        schedule_drawing(text, textx, texty);
      }      
    }
    i++;
  }
  
  
  i=0;
  while (i < cha_count) {
    c = character[i];
    if (c.Room == player.Room && c.Clickable && c.ID != player.ID) {
      ViewFrame *cviewframe = Game.GetViewFrame(c.View, c.Loop, c.Frame);
      textx = c.x-GetViewportX();
      texty = c.y - Game.SpriteHeight[cviewframe.Graphic]*c.Scaling/2*100-GetViewportY();
      text = c.Name;
      if(text.Length>2){
        schedule_drawing(text, textx, texty);
      }
    }
    i++;
  }
  
  draw_all_labels();
  interactionSurface.Release();
  interactionOverlay= Overlay.CreateGraphical(0, 0, interactionSprite.Graphic, true);
}

//hides the Interaction Detector Overlay
static void InteractionDetector::hide() { 
  interactionOverlay.Remove();
  interactionSprite.Delete();
  interactionShown = false;
}

//external function to check if the Interaction Detector Overlay is already shown
static bool InteractionDetector::isShown() {  
  return interactionShown;
}

//this allows toglin on and off the Interaction Detector Overlay
static void InteractionDetector::toggle() {  
  if(interactionShown){
    InteractionDetector.hide();
  } else {
    InteractionDetector.show();
  }
}



//everytime a room is loaded, we must recalculate it's hotspot position.
function on_event (EventType event, int data)
{
  if (event == eEventLeaveRoom){
    if(interactionShown){
      InteractionDetector.hide();
    }
  } 
}

[close]

Crimson Wizard

#1
The thing that brought my attention immediately, DrawingSurface must be released each time you finished drawing. You should not be keeping DrawingSurface pointer as a global variable (you may, but that makes little sense), instead create it locally every time you begin drawing and call Release in the end.

EDIT: I realized that draw_label is just a helper function that is called from "show", in that case you may use global variable, or pass surface as parameter, but anyway you should call Release after you finished drawing, e.g. right before creating overlay.

According to DrawingSurface's concept, the object (sprite, room background, etc) it is get from is not guaranteed to be updated until you call Release. There is a slight difference between Software (aka DirectDraw5) and hardware-accelerated renderers here (software renderer may get image updated without Release call), but they are related to difference in internal work of these renderers.

eri0o

Hey Crimson Wizard ! Thanks for the help, I moved the release to the show function, unfortunately, it's not that.

I can draw both characters name and objects name. Just the hotspots, they are drawn in position 500,500 (500 is probably the result of 999/2 rounded up as int, outside of the screen, my screen is 320x180).

Khris

I found the problem; you forgot to reset x:

Code: ags
  while (y < System.ViewportHeight) {
    x = 0;  // ADD THIS
    while (x < System.ViewportWidth) {

eri0o

Khris you are awesome. Thanks!

EDIT:

Holy Damn, I have more than 20 interactive things per screen. :-\

SMF spam blocked by CleanTalk