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.ascSpoiler
// 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();
}
}
Fullcode InteractionDetector.ashSpoiler
// 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();
};
EDIT:
Fixed Code Below! Thanks Khris!
InteractionDetector.asc
Spoiler
// 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();
}
}
InteractionDetector.ash
Spoiler
// 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();
};
EDIT2:
This is my final version that solves label colision:
InteractionDetector.asc
Spoiler
// 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();
}
}
}
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.