Object Follow Mouse Topic is solved

Get help with using AutoHotkey and its commands and hotkeys
User avatar
TheDewd
Posts: 1391
Joined: 19 Dec 2013, 11:16
Location: USA

Object Follow Mouse

30 Aug 2019, 10:29

I'm having trouble figuring out the correct code to make an object follow the mouse cursor around the screen.

I don't want the object to match the cursor position exactly though.

For example, if the user moves the mouse, the object will begin to move also, following the cursor. Once the object reaches the cursor, it stops.

Any ideas?

The closest examples I could find are linked below, although they're much faster than I would want. Should move SLOWLY...
https://processing.org/examples/easing.html
https://s.codepen.io/osublake/fullembedgrid/65cdc21ce5c49da45709ff9d09d0a754?type=embed&animations=run

Code: Select all

; Auto-Execute =================================================================
#SingleInstance, Force ; Allow only one running instance of script
#Persistent ; Keep the script permanently running until terminated
#NoEnv ; Avoid checking empty variables for environment variables
#Warn ; Enable warnings to assist with detecting common errors
;#NoTrayIcon ; Disable the tray icon of the script
SetWorkingDir, % A_ScriptDir ; Set the working directory of the script
SetBatchLines, -1 ; The speed at which the lines of the script are executed
SendMode, Input ; The method for sending keystrokes and mouse clicks
;DetectHiddenWindows, On ; The visibility of hidden windows by the script
;SetWinDelay, -1 ; The delay to occur after modifying a window
;SetControlDelay, -1 ; The delay to occur after modifying a control
OnExit("OnUnload") ; Run a subroutine or function when exiting the script
CoordMode, Mouse, Screen ; Sets coordinate mode for mouse

return ; End automatic execution
; ==============================================================================

; Functions ====================================================================
OnLoad() {
	Global ; Assume-global mode
	Static Init := OnLoad() ; Call function

	Menu, Tray, Tip, Demo
}

OnUnload(ExitReason, ExitCode) {
	Global ; Assume-global mode
}

GuiCreate() {
	Global ; Assume-global mode
	Static Init := GuiCreate() ; Call function

	Gui, +LastFound -Caption +AlwaysOnTop +HWNDhDemo
	Gui, Margin, 0, 0
	Gui, Color, EEAA99
	WinSet, TransColor, EEAA99
	Gui, Add, Picture, w32 h32 BackgroundTrans Icon1, Shell32.dll
	Gui, Show, w32 h32 NA, Demo

	SetTimer, CheckMouse
}

CheckMouse() {
	Global

	; Get position and size of Demo
	WinGetPos, WinX, WinY, WinW, WinH, ahk_id %hDemo%

	; Get position of mouse cursor
	MouseGetPos, MouseX, MouseY

	; Get cardinal direction of mouse cursor in relation to Demo
	If (MouseX > (WinX + WinW)) {
		Direction := (MouseY < WinY ? "NE" : MouseY > (WinY + WInH) ? "SE" : "E")
	} Else If (MouseX < WinX) {
		Direction := (MouseY < WinY ? "NW" : MouseY > (WinY + WinH) ? "SW" : "W")
	} Else If (MouseY < WinY) {
		Direction := "N"
	} Else If (MouseY > (WinY + WinH)) {
		Direction := "S"
	}
	
	ToolTip, % Direction

	;Gui, Show, % "x" WinX + 16 " y" WinY + 0 " NA" ; Move Right

	Sleep, 250
}

GuiClose(hDemo) {
	ExitApp ; Terminate the script unconditionally
}
; ==============================================================================
Image Bulldozer - Sokoban inspired game from 1994 recreated in AutoHotkey.
teadrinker
Posts: 1630
Joined: 29 Mar 2015, 09:41
Contact:

Re: Object Follow Mouse

30 Aug 2019, 11:12

Try:

Code: Select all

SetBatchLines, -1
CoordMode, Mouse

hGui := CreateGui()
MouseGetPos, X, Y
Gui, %hGui%:Show, x%X% y%Y% NA

SetHook(WH_MOUSE_LL := 14, "LowLevelMouseProc", hGui)

CreateGui() {
   Gui, New, -Caption +ToolWindow +AlwaysOnTop +hwndhGui
   Gui, Color, Red
   Gui, Show, w30 h30 HIDE
   Return hGui
}

SetHook(hook, fn := "", eventInfo := "", isGlobal := true) {
   static exitFunc
   if !fn {
      res := DllCall("UnhookWindowsHookEx", "Ptr", hook)
      OnExit(exitFunc, 0)
      exitFunc := ""
   }
   else {
      hHook := DllCall("SetWindowsHookEx", "Int", hook, "Ptr", RegisterCallback(fn, "Fast", 3, eventInfo)
                                         , "Ptr", !isGlobal ? 0 : DllCall("GetModuleHandle", "UInt", 0, "Ptr")
                                         , "UInt", isGlobal ? 0 : DllCall("GetCurrentThreadId"), "Ptr")
      ( exitFunc && OnExit(exitFunc, 0) )
      exitFunc := Func(A_ThisFunc).Bind(hHook, "")
      OnExit(exitFunc)
      Return hHook
   }
}

LowLevelMouseProc(nCode, wParam, lParam) {
   static WM_MOUSEMOVE := 0x200, coords := [], timer := Func("LowLevelMouseProc").Bind("timer", "", ""), hGui
   if (nCode != "timer") {
      if (wParam = WM_MOUSEMOVE)  {
         mouseX := NumGet(lParam + 0, "Int")
         mouseY := NumGet(lParam + 4, "Int")
         coords.Push([mouseX, mouseY])
         (!hGui && hGui := A_EventInfo)
         SetTimer, % timer, -10
      }
      Return DllCall("CallNextHookEx", Ptr, 0, Int, nCode, Ptr, wParam, Ptr, lParam)
   }
   else {
      while coords[1] {
         point := coords.RemoveAt(1)
         Gui, %hGui%: Show, % "NA x" point[1] . " y" point[2]
         Sleep, 10
      }
   }
}
User avatar
TheDewd
Posts: 1391
Joined: 19 Dec 2013, 11:16
Location: USA

Re: Object Follow Mouse

30 Aug 2019, 11:28

@teadrinker,

Thanks for your reply. Much appreciated, but it isn't exactly what I'm looking for.

I don't want to record & play the mouse path using another object. I'd like another object to "chase" the mouse cursor independently using the relational coordinates between the object and mouse cursor.

I'm trying to create something EXACTLY like this example: https://webneko.net/

Click the cat in the top-left corner to start following the mouse.

As the mouse cursor is moved, the object should slowly follow the cursor by calculating the position, distance between, etc.
Image Bulldozer - Sokoban inspired game from 1994 recreated in AutoHotkey.
User avatar
boiler
Posts: 5219
Joined: 21 Dec 2014, 02:44

Re: Object Follow Mouse

30 Aug 2019, 13:20

@TheDewd - It seems like the code you posted with some modifications pretty much does what you wanted. Does the version below start to approximate what you were looking for?

Code: Select all

; Auto-Execute =================================================================
#SingleInstance, Force ; Allow only one running instance of script
#Persistent ; Keep the script permanently running until terminated
#NoEnv ; Avoid checking empty variables for environment variables
#Warn ; Enable warnings to assist with detecting common errors
;#NoTrayIcon ; Disable the tray icon of the script
SetWorkingDir, % A_ScriptDir ; Set the working directory of the script
SetBatchLines, -1 ; The speed at which the lines of the script are executed
SendMode, Input ; The method for sending keystrokes and mouse clicks
;DetectHiddenWindows, On ; The visibility of hidden windows by the script
;SetWinDelay, -1 ; The delay to occur after modifying a window
;SetControlDelay, -1 ; The delay to occur after modifying a control
OnExit("OnUnload") ; Run a subroutine or function when exiting the script
CoordMode, Mouse, Screen ; Sets coordinate mode for mouse

return ; End automatic execution
; ==============================================================================

; Functions ====================================================================
OnLoad() {
	Global ; Assume-global mode
	Static Init := OnLoad() ; Call function

	Menu, Tray, Tip, Demo
}

OnUnload(ExitReason, ExitCode) {
	Global ; Assume-global mode
}

GuiCreate() {
	Global ; Assume-global mode
	Static Init := GuiCreate() ; Call function

	Gui, +LastFound -Caption +AlwaysOnTop +HWNDhDemo
	Gui, Margin, 0, 0
	Gui, Color, EEAA99
	WinSet, TransColor, EEAA99
	Gui, Add, Picture, w32 h32 BackgroundTrans Icon1, Shell32.dll
	Gui, Show, w32 h32 NA, Demo

	SetTimer, CheckMouse
}

CheckMouse() {
	Global

	; Get position and size of Demo
	WinGetPos, WinX, WinY, WinW, WinH, ahk_id %hDemo%

	; Get position of mouse cursor
	MouseGetPos, MouseX, MouseY

	; Get cardinal direction of mouse cursor in relation to Demo
	If (MouseX > (WinX + WinW)) {
		Direction := (MouseY < WinY ? "NE" : MouseY > (WinY + WInH) ? "SE" : "E")
	} Else If (MouseX < WinX) {
		Direction := (MouseY < WinY ? "NW" : MouseY > (WinY + WinH) ? "SW" : "W")
	} Else If (MouseY < WinY) {
		Direction := "N"
	} Else If (MouseY > (WinY + WinH)) {
		Direction := "S"
	}
	
	ToolTip, % Direction
	NewX := WinX
	NewY := WinY
	if InStr(Direction, "E")
		NewX := WinX + 16 ; Move Right
	if InStr(Direction, "W")
		NewX := WinX - 16 ; Move Left
	if InStr(Direction, "N")
		NewY := WinY - 16 ; Move Up
	if InStr(Direction, "S")
		NewY := WinY + 16 ; Move Down

	Gui, Show, % "x" NewX " y" NewY " NA"
	Sleep, 250
}

GuiClose(hDemo) {
	ExitApp ; Terminate the script unconditionally
}
; ==============================================================================
User avatar
TheDewd
Posts: 1391
Joined: 19 Dec 2013, 11:16
Location: USA

Re: Object Follow Mouse

30 Aug 2019, 13:30

@boiler,

That's extremely close to what I want.

However, the object should be able to move freely in any direction, not just the 8 directions.

See https://webneko.net/ again. If you move the cursor to a side, and then *slowly* up or down, the cat will move in an angle that doesn't have to be a straight diagonal.

Any ideas about that? This is where I face the issue.

Thank you!

I have the C++ source code of the original 1995/1998 Windows application which includes the movement functions, if that helps:

Code: Select all

// Neko.cpp: implementation of the CNeko class.
//
//////////////////////////////////////////////////////////////////////

#include <windows.h>
#include "NekoCommon.h"
#include "NekoSettings.h"
#include "Neko.h"
#include "resource.h"
#include <math.h>

//maths calculations
#define g_dSinPiPer8        0.3826834323651  // sin [pi/8]
#define g_dSinPiPer8Times3  0.9238795325113  // sin ( [pi/8] x 3 )

//misc. constants
#define MAX_TICK        9999 //Odd Only

//animation control constants
#define STOP_TIME      4
#define WASH_TIME      10
#define SCRATCH_TIME   4
#define YAWN_TIME      3
#define AWAKE_TIME     3
#define CLAW_TIME      10

//external system variable
extern HINSTANCE g_hInstance;


//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CNeko::CNeko( char* lpszName )
{
	//store pet
	m_pPet = NULL;

    //plug icons into animation table
	m_nAnimation[STOP][0]	 = 28;      m_nAnimation[STOP][1] =	    28;
	m_nAnimation[WASH][0]	 = 25;      m_nAnimation[WASH][1] =	    28;
	m_nAnimation[SCRATCH][0] = 26;      m_nAnimation[SCRATCH][1] =  27;
	m_nAnimation[YAWN][0]	 = 29;      m_nAnimation[YAWN][1] =	    29;
	m_nAnimation[SLEEP][0]	 = 30;      m_nAnimation[SLEEP][1] =	31;
	m_nAnimation[AWAKE][0]	 = 0;	    m_nAnimation[AWAKE][1] =	0;
	m_nAnimation[U_MOVE][0]  = 1;	    m_nAnimation[U_MOVE][1] =	2;
	m_nAnimation[D_MOVE][0]  = 9;	    m_nAnimation[D_MOVE][1] =	10;
	m_nAnimation[L_MOVE][0]  = 13;      m_nAnimation[L_MOVE][1] =	14;
	m_nAnimation[R_MOVE][0]  = 5;	    m_nAnimation[R_MOVE][1] =	6;
	m_nAnimation[UL_MOVE][0] = 15;      m_nAnimation[UL_MOVE][1] =  16;
	m_nAnimation[UR_MOVE][0] = 3;	    m_nAnimation[UR_MOVE][1] =  4;
	m_nAnimation[DL_MOVE][0] = 11;      m_nAnimation[DL_MOVE][1] =  12;
	m_nAnimation[DR_MOVE][0] = 7;	    m_nAnimation[DR_MOVE][1] =  8;
	m_nAnimation[U_CLAW][0]  = 17;      m_nAnimation[U_CLAW][1] =	18;
	m_nAnimation[D_CLAW][0]  = 23;      m_nAnimation[D_CLAW][1] =	24;
	m_nAnimation[L_CLAW][0]  = 21;      m_nAnimation[L_CLAW][1] =	22;
	m_nAnimation[R_CLAW][0]  = 19;      m_nAnimation[R_CLAW][1] =	20;

    //set variables
	m_nDX = m_nDY = 0;
	strcpy( m_szName, lpszName );
    m_dwSpeed = 16;
    m_dwIdleSpace = 6;
    m_Action = CHASE_MOUSE;
    m_nActionCount = 0;
    *m_szFootprintLibname = '\0';
    *m_szLibname = '\0';
    m_bFootprints = FALSE;

	strcpy( m_szSndIdle1, "" );
	strcpy( m_szSndIdle2, "" );
	strcpy( m_szSndIdle3, "" );
	strcpy( m_szSndSleep, "" );
	strcpy( m_szSndAwake, "" );
	m_dwSndFrequency = 0;
    m_dwScale = 100;

	//build configuration registry key
	char szKey[1024];
	strcpy( szKey, szNekoRegKey );
	if( strlen( m_szName ) > 0 )
	{
		strcat( szKey, "\\" );
		strcat( szKey, m_szName );
	}

	//load configuration
    CNekoSettings NekoSettings( szKey, (strlen(m_szName) == 0) );
	if( NekoSettings.IsOpen() )
	{
		//load in all of the settings
		NekoSettings.GetInt( szNekoScaleKey,		&m_dwScale );
		NekoSettings.GetInt( szNekoSpeedKey,		&m_dwSpeed );
		NekoSettings.GetInt( szNekoSenseKey,		&m_dwIdleSpace );
		NekoSettings.GetString( szNekoLibraryKey,	m_szLibname, MAX_PATH-1 );
		NekoSettings.GetString( szNekoSndIdle1Key,	m_szSndIdle1, MAX_PATH-1 );
		NekoSettings.GetString( szNekoSndIdle2Key,	m_szSndIdle2, MAX_PATH-1 );
		NekoSettings.GetString( szNekoSndIdle3Key,	m_szSndIdle3, MAX_PATH-1 );
		NekoSettings.GetString( szNekoSndSleepKey,	m_szSndSleep, MAX_PATH-1 );
		NekoSettings.GetString( szNekoSndAwakeKey,	m_szSndAwake, MAX_PATH-1 );
		NekoSettings.GetInt( szNekoSndFreqKey,		&m_dwSndFrequency );
        NekoSettings.GetBool( szNekoFootprintKey,   &m_bFootprints );
        NekoSettings.GetString( szNekoFootprintLibKey, m_szFootprintLibname, MAX_PATH-1 );

        DWORD dwAction = m_Action;
        NekoSettings.GetInt( szNekoActionKey, &dwAction );
        m_Action = dwAction;


        DWORD bAlwaysOnTop = FALSE;
		NekoSettings.GetInt( szNekoOnTopKey, &bAlwaysOnTop );

        //create the correct pet
        if( bAlwaysOnTop )
            m_pPet = new CAlwaysOnTopPet();
        else
            m_pPet = new CDesktopPet();
       
	}
    else
    {
        //configuration didn't open... create a desktop pet only
        m_pPet = new CDesktopPet();
    }

    //initialse footprint icons
    for( int i = 0; i < 8; i++ ) m_hIconFootprints[i] = NULL;

    //apply scaling
    m_pPet->SetScale( ((float)m_dwScale / 100.0f ) );

	//load the images
	BOOL fLoadProblems = FALSE;
	if( m_szLibname == NULL || *m_szLibname == '\0' || ((int)ExtractIcon( g_hInstance, m_szLibname, -1 ) < 32 ))
    {
        //use default images if there is no file or not enough icons
		GetModuleFileName( NULL, m_szLibname, MAX_PATH );
		fLoadProblems = !LoadImages();
    }
	else
	{
		//load all the icons in the file
		fLoadProblems = !LoadImages();
		if( fLoadProblems ) 
		{
			//use default images if it fails with the user's choice
			GetModuleFileName( NULL, m_szLibname, MAX_PATH );
			fLoadProblems = !LoadImages();
		}
	}

	/* FIXME:

		It appears that Windows offers no support for ExtractIcon
		on icons that are not 32x32 - this means that all icon
		libraries selected by the user to use will have their icons
		scaled down to 32x32. I have tried the following:

        1) Used LoadLibrary() to load the icon library chosen. This
		   worked on some, but not all. I then used LoadLibraryEx() 
		   and passed it the 'don't call DllMain' flags. This caused
		   the libraries that weren't working to work, and vice-versa.

		2) After LoadLibrary(), attempting to load all resource IDs
		   until 32 valid icons were loaded. This nearly worked, but
		   took ages and was therefor unacceptable. It also failed
		   with LoadLibrary() as in 1.

		3) Tried EnumResourceNames() for all icons. It only loaded some
		   of them and then gave up. At this point, so did I.

		Result: It is only possible to use 32x32 icons in Neko, although
		these can be scaled up or down as required, resulting in blockyness
		etc.

	*/

    //set initial state
	SetState( STOP );

    //set initial action
    m_nActionX = m_pPet->GetBoundsRect().left + ( rand() % (m_pPet->GetBoundsRect().right-(m_dwSpeed * 8)) );
    m_nActionY = m_pPet->GetBoundsRect().top + ( rand() % (m_pPet->GetBoundsRect().bottom-(m_dwSpeed * 8)) );
    m_nActionDX = ((( rand() % 2 ) ? 1 : -1) * (m_dwSpeed/2)) + 1;
	m_nActionDY = ((( rand() % 2 ) ? 1 : -1) * (m_dwSpeed/2)) + 1;

    //set initial position (random)
    m_nToX = m_pPet->GetBoundsRect().left + ( rand() % ( (m_pPet->GetBoundsRect().right- m_pPet->GetSize().cx) - m_pPet->GetBoundsRect().left ) ) ;
    m_nToY = m_pPet->GetBoundsRect().top + ( rand() % ( (m_pPet->GetBoundsRect().bottom - m_pPet->GetSize().cy) - m_pPet->GetBoundsRect().top ) );
	
	m_pPet->MoveTo( m_nToX, m_nToY );

	//deal with error (fixme?)
	/*
	if( fLoadProblems )
		throw ICON_EXCEPTION;
	*/
}

CNeko::~CNeko()
{
	delete m_pPet;
    for( int i = 0; i < 8; i++ ) if( m_hIconFootprints[i] ) DestroyIcon( m_hIconFootprints[i] );
}

BOOL CNeko::MoveStart()
{
    return( !(( m_nOldToX >= m_nToX-(int)m_dwIdleSpace ) && 
              ( m_nOldToX <= m_nToX+(int)m_dwIdleSpace ) &&
              ( m_nOldToY >= m_nToY-(int)m_dwIdleSpace ) && 
              ( m_nOldToY <= m_nToY+(int)m_dwIdleSpace )));
}

void CNeko::CalcDirection()
{
    State NewState;
    double LargeX, LargeY, Length, SinTheta;

    if( (m_nDX == 0) && (m_nDY == 0) )
        NewState = STOP;
    else 
    {
        LargeX = (double)m_nDX;
        LargeY = (double)(-m_nDY);
        Length = sqrt(LargeX * LargeX + LargeY * LargeY);
        SinTheta = LargeY / Length;

        if( m_nDX > 0 )
        {
            if( SinTheta > g_dSinPiPer8Times3 )
                NewState = U_MOVE;
            else 
                if( (SinTheta <= g_dSinPiPer8Times3 ) && ( SinTheta > g_dSinPiPer8 ) ) 
                    NewState = UR_MOVE;
                else 
                    if( (SinTheta <= g_dSinPiPer8) && (SinTheta > -(g_dSinPiPer8) ) ) 
                        NewState = R_MOVE;
                    else 
                        if( (SinTheta <= -(g_dSinPiPer8) ) && (SinTheta > -(g_dSinPiPer8Times3) ) ) 
                            NewState = DR_MOVE;
                        else 
                            NewState = D_MOVE;
        }
        else 
        {
            if( SinTheta > g_dSinPiPer8Times3 )
                NewState = U_MOVE;
            else 
                if( (SinTheta <= g_dSinPiPer8Times3) && (SinTheta > g_dSinPiPer8) ) 
                    NewState = UL_MOVE;
                else 
                    if( (SinTheta <= g_dSinPiPer8) && (SinTheta > -(g_dSinPiPer8) ) )
                        NewState = L_MOVE;
                    else 
                        if( (SinTheta <= -(g_dSinPiPer8)) && (SinTheta > -(g_dSinPiPer8Times3) ) ) 
                            NewState = DL_MOVE;
                        else 
                            NewState = D_MOVE;
        }
    }

    if( m_State != NewState ) SetState( NewState );

}

void CNeko::RunTowards(int nX, int nY)
{
	//store old and new target
    m_nOldToX = m_nToX; m_nOldToY = m_nToY;
    m_nToX = nX; m_nToY = nY;

	//calculate distance to target and set delta positions
    double dLargeX, dLargeY, dDoubleLength, dLength;
    dLargeX = (double)(m_nToX - m_pPet->GetPosition().x - (int)m_pPet->GetSize().cx / 2); //stop in middle of cursor
    dLargeY = (double)(m_nToY - m_pPet->GetPosition().y - (int)m_pPet->GetSize().cy + 1); //...and just above
    dDoubleLength = dLargeX * dLargeX + dLargeY * dLargeY;

    if( dDoubleLength != 0.0 ) 
    {
        dLength = sqrt( dDoubleLength );
        if( dLength <= (int)m_dwSpeed ) 
        {
			//less than top speed - jump the gap!
            m_nDX = (int)dLargeX;
            m_nDY = (int)dLargeY;
        } 
        else 
        {
			//more than top speed - run at top speed towards target
            m_nDX = (int)(((int)m_dwSpeed * dLargeX) / dLength );
            m_nDY = (int)(((int)m_dwSpeed * dLargeY) / dLength );
        }
    } 
    else //we're at the target - stop
		m_nDX = m_nDY = 0;

    //increment animation counter
    if ( ++m_uTickCount >= MAX_TICK ) m_uTickCount = 0;
    
    if ( m_uTickCount%2 == 0 )
        if (m_uStateCount < MAX_TICK) m_uStateCount++;

    //change state
    switch( m_State ) 
    {
        case STOP:
            if( MoveStart() ) 
                SetState( AWAKE ); 
            else
                if( m_uStateCount >= STOP_TIME ) 
                    if( m_nDX < 0 && m_pPet->GetPosition().x <= 0 ) SetState( L_CLAW ); 
                    else 
                        if( m_nDX > 0 && m_pPet->GetPosition().x >= ( m_pPet->GetBoundsRect().right - m_pPet->GetBoundsRect().left ) - m_pPet->GetSize().cx ) SetState( R_CLAW ); 
                        else 
                            if( m_nDY < 0 && m_pPet->GetPosition().y <= 0 ) SetState( U_CLAW ); 
                            else
                                if( m_nDY > 0 && m_pPet->GetPosition().y >= ( m_pPet->GetBoundsRect().bottom - m_pPet->GetBoundsRect().top ) - m_pPet->GetSize().cy ) SetState( D_CLAW );
                                else SetState( WASH );
			m_pPet->SetImage( GetStateAnimationFrameIndex() );
            break;

        case WASH:
            if( MoveStart() ) SetState( AWAKE ); 
                else if( m_uStateCount >= WASH_TIME ) SetState( SCRATCH );
			m_pPet->SetImage( GetStateAnimationFrameIndex() );
            break;

        case SCRATCH:
            if( MoveStart() ) SetState( AWAKE ); 
                else if (m_uStateCount >= SCRATCH_TIME ) SetState( YAWN );
			m_pPet->SetImage( GetStateAnimationFrameIndex() );
            break;

        case YAWN:
            if( MoveStart() )  SetState( AWAKE );
                else if (m_uStateCount >= YAWN_TIME) SetState( SLEEP );
			m_pPet->SetImage( GetStateAnimationFrameIndex() );
            break;
        
        case SLEEP:
            if( MoveStart() ) SetState( AWAKE );
			m_pPet->SetImage( GetStateAnimationFrameIndex() );
            break;
        
        case AWAKE:
            if( m_uStateCount >= (UINT)(AWAKE_TIME + (rand()%20)) ) CalcDirection();
			m_pPet->SetImage( GetStateAnimationFrameIndex() );
            break;
       
        case U_MOVE:
        case D_MOVE:
        case L_MOVE:
        case R_MOVE:
        case UL_MOVE:
        case UR_MOVE:
        case DL_MOVE:
        case DR_MOVE:
		{
			//make sure Neko does not go outside boundary area
            int nX = m_pPet->GetPosition().x, nY = m_pPet->GetPosition().y;
			int nNewX = nX + m_nDX, nNewY = nY + m_nDY;
            int nWidth = ( m_pPet->GetBoundsRect().right - m_pPet->GetBoundsRect().left ) - m_pPet->GetSize().cx;
            int nHeight = ( m_pPet->GetBoundsRect().bottom - m_pPet->GetBoundsRect().top ) - m_pPet->GetSize().cy;
			BOOL fOutside = ( nNewX <= 0 || nNewX >= nWidth || nNewY <= 0 || nNewY >= nHeight );

			//change the image and move Neko
	        CalcDirection();

            //clip new x and y positions and see if we've moved anywhere
            if( nNewX < 0 ) nNewX = 0; else if( nNewX > nWidth ) nNewX = nWidth;
            if( nNewY < 0 ) nNewY = 0; else if( nNewY > nHeight ) nNewY = nHeight;
            BOOL fNotMoved = ( nNewX == nX ) && ( nNewY == nY );

            //stop if we can't go any further
            if( fOutside && fNotMoved )
                SetState(STOP);
			else
            {
				m_pPet->SetImageAndMoveTo( GetStateAnimationFrameIndex(), nNewX, nNewY );
                if( m_bFootprints )
                {
                    int iFpAnim = -1;
                    switch( m_State )
                    {
                        case U_MOVE:  iFpAnim = 0; break;
                        case D_MOVE:  iFpAnim = 4; break;
                        case L_MOVE:  iFpAnim = 6; break;
                        case R_MOVE:  iFpAnim = 2; break;
                        case UL_MOVE: iFpAnim = 7; break;
                        case UR_MOVE: iFpAnim = 1; break;
                        case DL_MOVE: iFpAnim = 5; break;
                        case DR_MOVE: iFpAnim = 3; break;
                    }
                    if( iFpAnim != -1 )
                    {
                        if( m_uTickCount & 1 )
                            m_pPet->DrawOnTarget( nX-(m_nDY/2), nY, m_hIconFootprints[iFpAnim] );
                        else
                            m_pPet->DrawOnTarget( nX, nY-(m_nDX/2), m_hIconFootprints[iFpAnim] );
                    }
                }
            }
            break;
		}
        
        case U_CLAW:
        case D_CLAW:
        case L_CLAW:
        case R_CLAW:
            if( MoveStart() ) SetState( AWAKE );
                else if( m_uStateCount >= CLAW_TIME ) SetState( SCRATCH );
			m_pPet->SetImage( GetStateAnimationFrameIndex() );
            break;

        default:
            //something bad has happened!
            MessageBeep( 0xFFFFFFFF );
            SetState( STOP );
			m_pPet->SetImage( GetStateAnimationFrameIndex() );
            break;
    }
}

int CNeko::GetStateAnimationFrameIndex()
{
    if ( m_State != SLEEP )
		return m_nAnimation[m_State][m_uTickCount & 0x1];
    else 
        return m_nAnimation[m_State][(m_uTickCount>>2) & 0x1];

}

void CNeko::SetState( State state )
{
    //reset the animation counters
    m_uTickCount = 0;
    m_uStateCount = 0;

    //update the state
    m_State = state;
}


BOOL CNeko::LoadImages()
{
	/* Note: The icons should be in the following order in the file:

	    Awake
		Up 1
		Up 2
		Up Right 1
		Up Right 2
		Right 1
		Right 2
		Down Right 1
		Down Right 2
		Down 1
		Down 2
		Down Left 1
		Down Left 2
		Left 1
		Left 2
		Up Left 1
		Up Left 2
		Up Claw 1
		Up Claw 2
		Right Claw 1
		Right Claw 2
		Left Claw 1
		Left Claw 2
		Down Claw 1
		Down Claw 2
		Wash 2
		Scratch 1
		Scratch 2
		Yawn 1
		Yawn 2
		Sleep 1
		Sleep 2
	*/

	//load the icons
	int n;
	HICON hIcons[32];
	for( n = 0; n < 32; n++ )
		hIcons[n] =  ExtractIcon( g_hInstance, m_szLibname, n );

	//check last icon
	if( (UINT)hIcons[31] <= 1 )
	{
		//error - delete all icons
		for( n = 0; n < 32; n++ ) DestroyIcon( hIcons[n] );

        char szBuffer[1024];
        wsprintf( szBuffer, "There are not enough icons in this icon library\n%s\nIt must contain at least 32 icons", m_szLibname );
        MessageBox( NULL, szBuffer, "Error", MB_ICONERROR|MB_TASKMODAL );
		return FALSE;
	}

	//apply icons
	m_pPet->SetImages( hIcons, 32 );

    //destroy icon table
    for( n = 0; n < 32; n++ ) DestroyIcon( hIcons[n] );

    //load footprints
    if( m_bFootprints )
    {
        if( *m_szFootprintLibname )
            for( n = 0; n < 8; n++ ) m_hIconFootprints[n] = ExtractIcon( g_hInstance, m_szFootprintLibname, n );
        else
        {
            UINT uID[] = { IDI_FP_UP, IDI_FP_UPRIGHT, IDI_FP_RIGHT, IDI_FP_DOWNRIGHT, IDI_FP_DOWN, IDI_FP_DOWNLEFT, IDI_FP_LEFT, IDI_FP_UPLEFT };
            for( n = 0; n < 8; n++ ) m_hIconFootprints[n] = LoadIcon( g_hInstance, MAKEINTRESOURCE(uID[n]) );
        }
    }

	return TRUE;
}

/********************************************************************************************************/

void CNeko::Update()
{
    //apply VVPAI (very, very poor artificial intelligence!!!)
    switch( m_Action )
    {
        default:
        case CHASE_MOUSE:
        {
	        POINT pt;
	        GetCursorPos( &pt );
	        RunTowards( pt.x, pt.y );
            break;
        }

        case RUN_AWAY_FROM_MOUSE:
        {
            POINT pt;
            int xdiff, ydiff;
            GetCursorPos( &pt );
			DWORD dwLimit = m_dwIdleSpace*16;

			xdiff = ( m_pPet->GetPosition().x + (m_pPet->GetSize().cx/2) ) - pt.x;
			ydiff = ( m_pPet->GetPosition().y + (m_pPet->GetSize().cy/2) ) - pt.y;

			if( abs(xdiff) < (int)dwLimit && abs(ydiff) < (int)dwLimit )
			{
				//mouse cursor is too close
				int x, y;
				double dLength = sqrt((double)xdiff*xdiff + ydiff*ydiff);
				if( dLength != 0.0 )
				{
					x = m_pPet->GetPosition().x + (int)((xdiff / dLength) * dwLimit);
					y = m_pPet->GetPosition().y + (int)((ydiff / dLength) * dwLimit);
				}
				else
					x = y = 32;

				//make Neko run away from the mouse
                RunTowards( x, y );
                if( m_State == AWAKE ) CalcDirection(); //don't show awake animation
			}
			else
				RunTowards( m_nToX, m_nToY ); //keep running...
			break;
		}

        case RUN_AROUND_RANDOMLY:
        {
            if( m_State == SLEEP) m_nActionCount++;
            if( m_nActionCount > (int)m_dwIdleSpace*10 )
            {
                m_nActionCount = 0;
                RunTowards( m_pPet->GetBoundsRect().left + (rand() % (m_pPet->GetBoundsRect().right-m_pPet->GetBoundsRect().left)), m_pPet->GetBoundsRect().top + (rand() % (m_pPet->GetBoundsRect().bottom-m_pPet->GetBoundsRect().top)) );
            }
            else
                RunTowards( m_nToX, m_nToY );
            break;
        }

        case PACE_AROUND_SCREEN:
        {
            if( (m_nDX == 0) && (m_nDY == 0) ) m_nActionCount = ( m_nActionCount + 1 ) % 4;
            switch( m_nActionCount )
            {
                case 0: RunTowards( m_pPet->GetBoundsRect().left + m_pPet->GetSize().cx, m_pPet->GetBoundsRect().top + m_pPet->GetSize().cy ); break;
                case 1: RunTowards( m_pPet->GetBoundsRect().left + m_pPet->GetSize().cx, m_pPet->GetBoundsRect().bottom - m_pPet->GetSize().cy ); break;
                case 2: RunTowards( m_pPet->GetBoundsRect().right - m_pPet->GetSize().cx, m_pPet->GetBoundsRect().bottom - m_pPet->GetSize().cy ); break;
                case 3: RunTowards( m_pPet->GetBoundsRect().right - m_pPet->GetSize().cx, m_pPet->GetBoundsRect().top + m_pPet->GetSize().cy ); break;
            }
            break;
        }

        case RUN_AROUND:
        {
			//bounding box repel border
			DWORD dwBoundingBox = m_dwSpeed * 8;

			//move invisible ball
            m_nActionX += m_nActionDX;
            m_nActionY += m_nActionDY;
			
			//repel invisible ball from the edges of the screen.
			if( m_nActionX < (int)(m_pPet->GetBoundsRect().left + dwBoundingBox) )
				if( m_nActionX > m_pPet->GetBoundsRect().left ) m_nActionDX++; else m_nActionDX = -m_nActionDX;
			else
				if( m_nActionX > (int)(m_pPet->GetBoundsRect().right - dwBoundingBox) )
					if( m_nActionX < m_pPet->GetBoundsRect().right ) m_nActionDX--; else m_nActionDX = -m_nActionDX;

			if( m_nActionY < (int)(m_pPet->GetBoundsRect().top + dwBoundingBox) )
				if( m_nActionY > m_pPet->GetBoundsRect().top ) m_nActionDY++; else m_nActionDY = -m_nActionDY;
			else
				if( m_nActionY > (int)(m_pPet->GetBoundsRect().bottom - dwBoundingBox) )
					if( m_nActionY < m_pPet->GetBoundsRect().bottom ) m_nActionDY--; else m_nActionDY = -m_nActionDY;

			//tell Neko to run towards the new point
            RunTowards( m_nActionX, m_nActionY );
            break;
        }
    }

    //play idle sounds
    if( m_dwSndFrequency )
    {
        if( (DWORD)(rand()%100) <= m_dwSndFrequency )
        {
            switch( GetState() )
            {
                case AWAKE:
                    PlaySound( m_szSndAwake, NULL, SND_NOSTOP|SND_NOWAIT|SND_FILENAME|SND_NODEFAULT|SND_ASYNC );
                    break;

                case SLEEP:
                    PlaySound( m_szSndSleep, NULL, SND_NOSTOP|SND_NOWAIT|SND_FILENAME|SND_NODEFAULT|SND_ASYNC );
                    break;

				default:
                    switch( rand()%3 )
                    {
                        case 0:  PlaySound( m_szSndIdle1, NULL, SND_NOSTOP|SND_NOWAIT|SND_FILENAME|SND_NODEFAULT|SND_ASYNC ); break;
                        case 1:  PlaySound( m_szSndIdle2, NULL, SND_NOSTOP|SND_NOWAIT|SND_FILENAME|SND_NODEFAULT|SND_ASYNC ); break;
                        default: PlaySound( m_szSndIdle3, NULL, SND_NOSTOP|SND_NOWAIT|SND_FILENAME|SND_NODEFAULT|SND_ASYNC ); break;
                    }
                break;
            }
        }
    }
}
Last edited by TheDewd on 30 Aug 2019, 13:36, edited 1 time in total.
Image Bulldozer - Sokoban inspired game from 1994 recreated in AutoHotkey.
User avatar
boiler
Posts: 5219
Joined: 21 Dec 2014, 02:44

Re: Object Follow Mouse

30 Aug 2019, 13:36

Ah, yes. I didn't notice that it made that fine of a distinction in direction. I think I can modify the math to make it do something similar to that. I'll probably post something later that takes the angle into account in its movement.
teadrinker
Posts: 1630
Joined: 29 Mar 2015, 09:41
Contact:

Re: Object Follow Mouse

30 Aug 2019, 14:18

Code: Select all

SetBatchLines, -1
CoordMode, Mouse
startGuiX := 300, startGuiY := 300

Gui, New, -Caption +ToolWindow +AlwaysOnTop +hwndhGui
Gui, Color, Red
Gui, Show, x%startGuiX% y%startGuiY% w30 h30
OnMessage( 0x201, Func("WM_LBUTTONDOWN").Bind(hGui, startGuiX, startGuiY) )

WM_LBUTTONDOWN(hGui, startGuiX, startGuiY, wp, lp, msg, hwnd) {
   static toggle := false, timer1, timer2
   if (hGui = hwnd) {
      if (toggle := !toggle) {
         ( !timer1 && timer1 := Func("FollowCursor").Bind(hGui) )
         GoHome()
         try SetTimer, % timer2, Off
         SetTimer, % timer1, 10
      }
      else {
         ( !timer2 && timer2 := Func("GoHome").Bind(false, hGui, startGuiX, startGuiY) )
         try SetTimer, % timer1, Off
         SetTimer, % timer2, 10
      }
   }
}

FollowCursor(hGui) {
   static prev_mouseX, prev_mouseY, coords, prevCreateTime := 0
   MouseGetPos, mouseX, mouseY
   if ( (A_TickCount - prevCreateTime > 100) && (mouseX != prev_mouseX || mouseY != prev_mouseY) ) {
      prevCreateTime := A_TickCount
      WinGetPos, guiX, guiY,,, ahk_id %hGui%
      coords := CreateCoords([guiX, guiY], [mouseX, mouseY])
      prev_mouseX := mouseX, prev_mouseY := mouseY
   }
   if coords[1] {
      point := coords.RemoveAt(1)
      Gui, %hGui%: Show, % "NA x" point[1] " y" point[2]
   }
}

GoHome(deleteCoords := true, hGui := "", startGuiX := "", startGuiY := "") {
   static coords
   if deleteCoords {
      coords := ""
      Return
   }
   if !coords {
      WinGetPos, guiX, guiY,,, ahk_id %hGui%
      coords := CreateCoords([guiX, guiY], [startGuiX, startGuiY])
   }
   if coords[1] {
      point := coords.RemoveAt(1)
      Gui, %hGui%: Show, % "NA x" point[1] " y" point[2]
   }
   else {
      SetTimer,, Off
      coords := ""
   }
}

CreateCoords(point1, point2) {
   distance := ((point2[1] - point1[1])**2 + (point2[2] - point1[2])**2)**.5/3
   coords := []
   stepX := Abs((point2[1] - point1[1])/distance)
   stepY := Abs((point2[2] - point1[2])/distance)
   Loop % distance
      coords.Push( [point1[1] + stepX*(A_Index - 1)*(point2[1] > point1[1] ? 1 : -1)
                  , point1[2] + stepY*(A_Index - 1)*(point2[2] > point1[2] ? 1 : -1)] )
   Return coords
}
User avatar
TheDewd
Posts: 1391
Joined: 19 Dec 2013, 11:16
Location: USA

Re: Object Follow Mouse

30 Aug 2019, 14:34

@teadrinker,

That definitely does what I want, but I'm not sure how to tweak it, for example to move in 16px increments instead of 1px -- similarly to the linked website.

It's very fast and fluid at the moment.

Impressive solution! :shock:

I also like the addition of the "Go Home" feature. :lol:

Note: If it's not clear by now, I'm attempting to create "Neko" in AutoHotkey -- Just couldn't figure out how to get the mouse movement to work as expected. :D
Last edited by TheDewd on 30 Aug 2019, 14:39, edited 2 times in total.
Image Bulldozer - Sokoban inspired game from 1994 recreated in AutoHotkey.
User avatar
boiler
Posts: 5219
Joined: 21 Dec 2014, 02:44

Re: Object Follow Mouse  Topic is solved

30 Aug 2019, 14:36

Here's a change to the version I was working on to take into account the actual angle. It moves 16 pixels at a time in the direction of the mouse pointer.

Code: Select all

; Auto-Execute =================================================================
#SingleInstance, Force ; Allow only one running instance of script
#Persistent ; Keep the script permanently running until terminated
#NoEnv ; Avoid checking empty variables for environment variables
#Warn ; Enable warnings to assist with detecting common errors
;#NoTrayIcon ; Disable the tray icon of the script
SetWorkingDir, % A_ScriptDir ; Set the working directory of the script
SetBatchLines, -1 ; The speed at which the lines of the script are executed
SendMode, Input ; The method for sending keystrokes and mouse clicks
;DetectHiddenWindows, On ; The visibility of hidden windows by the script
;SetWinDelay, -1 ; The delay to occur after modifying a window
;SetControlDelay, -1 ; The delay to occur after modifying a control
OnExit("OnUnload") ; Run a subroutine or function when exiting the script
CoordMode, Mouse, Screen ; Sets coordinate mode for mouse

return ; End automatic execution
; ==============================================================================

; Functions ====================================================================
OnLoad() {
	Global ; Assume-global mode
	Static Init := OnLoad() ; Call function

	Menu, Tray, Tip, Demo
}

OnUnload(ExitReason, ExitCode) {
	Global ; Assume-global mode
}

GuiCreate() {
	Global ; Assume-global mode
	Static Init := GuiCreate() ; Call function

	Gui, +LastFound -Caption +AlwaysOnTop +HWNDhDemo
	Gui, Margin, 0, 0
	Gui, Color, EEAA99
	WinSet, TransColor, EEAA99
	Gui, Add, Picture, w32 h32 BackgroundTrans Icon1, Shell32.dll
	Gui, Show, w32 h32 NA, Demo

	SetTimer, CheckMouse
}

CheckMouse() {
	Global

	; Get position and size of Demo
	WinGetPos, WinX, WinY, WinW, WinH, ahk_id %hDemo%

	; Get position of mouse cursor
	MouseGetPos, MouseX, MouseY

	; Get cardinal direction of mouse cursor in relation to Demo
	If (MouseX > (WinX + WinW)) {
		Direction := (MouseY < WinY ? "NE" : MouseY > (WinY + WInH) ? "SE" : "E")
	} Else If (MouseX < WinX) {
		Direction := (MouseY < WinY ? "NW" : MouseY > (WinY + WinH) ? "SW" : "W")
	} Else If (MouseY < WinY) {
		Direction := "N"
	} Else If (MouseY > (WinY + WinH)) {
		Direction := "S"
	}
	
	
	NewX := WinX
	NewY := WinY
	
	Angle := ATan(Abs(MouseY - WinY)/Abs(MouseX -WinX))
	DeltaX := 16 * Cos(Angle)
	DeltaY := 16 * Sin(Angle)

	ToolTip, % Direction "`n" Round(Angle * 180/3.14159) " deg"

	if InStr(Direction, "E")
		NewX := WinX + DeltaX ; Move Right
	if InStr(Direction, "W")
		NewX := WinX - DeltaX ; Move Left
	if InStr(Direction, "N")
		NewY := WinY - DeltaY ; Move Up
	if InStr(Direction, "S")
		NewY := WinY + DeltaY ; Move Down

	Gui, Show, % "x" NewX " y" NewY " NA"
	Sleep, 250
}

GuiClose(hDemo) {
	ExitApp ; Terminate the script unconditionally
}
; ==============================================================================
User avatar
TheDewd
Posts: 1391
Joined: 19 Dec 2013, 11:16
Location: USA

Re: Object Follow Mouse

30 Aug 2019, 14:37

@boiler,
That's EXACTLY what I wanted. Thank you so much! This should allow me to continue work on the script.

@teadrinker,
You've definitely taught me (by example) a new way to track the mouse distance from an object. Much appreciated.
Image Bulldozer - Sokoban inspired game from 1994 recreated in AutoHotkey.
User avatar
boiler
Posts: 5219
Joined: 21 Dec 2014, 02:44

Re: Object Follow Mouse

30 Aug 2019, 14:46

@TheDewd,
Glad to help. This was a very complicated case. You know, a lotta ins, lotta outs, lotta what-have-you's. :D
User avatar
SpeedMaster
Posts: 385
Joined: 12 Nov 2016, 16:09

Re: Object Follow Mouse

31 Aug 2019, 06:01

Just another example inside a Gui :)
homing missile (by Hellbent)
https://www.autohotkey.com/boards/viewtopic.php?p=183290#p183290

Regards
teadrinker
Posts: 1630
Joined: 29 Mar 2015, 09:41
Contact:

Re: Object Follow Mouse

31 Aug 2019, 09:17

boiler wrote:

Code: Select all

	; Get cardinal direction of mouse cursor in relation to Demo
	If (MouseX > (WinX + WinW)) {
		Direction := (MouseY < WinY ? "NE" : MouseY > (WinY + WInH) ? "SE" : "E")
	} Else If (MouseX < WinX) {
		Direction := (MouseY < WinY ? "NW" : MouseY > (WinY + WinH) ? "SW" : "W")
	} Else If (MouseY < WinY) {
		Direction := "N"
	} Else If (MouseY > (WinY + WinH)) {
		Direction := "S"
	}
	
	
	NewX := WinX
	NewY := WinY
	
	Angle := ATan(Abs(MouseY - WinY)/Abs(MouseX -WinX))
	DeltaX := 16 * Cos(Angle)
	DeltaY := 16 * Sin(Angle)

	ToolTip, % Direction "`n" Round(Angle * 180/3.14159) " deg"
IMO, it's wrong. :think:
What I see:

Image

Does 3 deg correspond to NE? I think this should be E.

One more my try:

Code: Select all

SetBatchLines, -1
CoordMode, Mouse
guiHomeX := 300, guiHomeY := 300
step := 30
period := 200

Gui, New, -Caption +ToolWindow +AlwaysOnTop +hwndhGui
Gui, Color, Red
Gui, Font, s16 q5, Calibri
Gui, Add, Text, x0 y2 w30 h30 Center, 0
Gui, Show, x%guiHomeX% y%guiHomeY% w30 h30
OnMessage( 0x201, Func("WM_LBUTTONDOWN").Bind(hGui, guiHomeX, guiHomeY, period, step) )

Esc:: ExitApp

WM_LBUTTONDOWN(hGui, guiHomeX, guiHomeY, period, step, wp, lp, msg, hwnd) {
   static toggle := true, timer, mode := []
   if (hGui = hwnd) {
      ( !timer && timer := Func("MoveGui").Bind(mode, hGui, step, guiHomeX, guiHomeY) )
      if toggle := !toggle
         mode[1] := "goHome" ; move to guiHomeX, guiHomeY
      else {
         mode[1] := "follow" ; follow cursor
         SetTimer, % timer, % period
      }
   }
}

MoveGui(mode, hGui, step, guiHomeX, guiHomeY) {
   static prevDirection
   if (mode[1] = "follow")
      MouseGetPos, mouseX, mouseY
   WinGetPos, guiX, guiY, guiW, guiH, ahk_id %hGui%
   guiX += guiW/2, guiY += guiH/2
   nextPoint := GetNextPoint( {x: guiX, y: guiY}
                            , mode[1] = "follow" ? {x: mouseX, y: mouseY} : {x: guiHomeX, y: guiHomeY}
                            , step, direction )
   if (direction != prevDirection) {
      GuiControl, %hGui%:, Static1, % direction
      prevDirection := direction
   }
   Gui, %hGui%: Show, % "NA x" nextPoint.x - guiW/2 " y" nextPoint.y - guiH/2
   if (mode[1] = "goHome" && nextPoint.x = guiHomeX && nextPoint.y = guiHomeY) {
      SetTimer,, Off
      GuiControl, %hGui%:, Static1, 0
   }
}

GetNextPoint(point1, point2, step, ByRef direction) {
   X := Floor(point2.x - point1.x)
   Y := Floor(point2.y - point1.y)
   distance := (X**2 + Y**2)**.5
   if ( X = 0 && Y = 0 )
      direction := 0
   else {
      sin := Y/distance
      cos := X/distance
      (sin < -0.365 && direction := "N") ; 0.365 is sin 22.5 (45/2) degree
      (sin >  0.365 && direction := "S")
      if direction {
         (cos < -0.365 && direction .= "W")
         (cos >  0.365 && direction .= "E")
      }
      else {
         (cos < 0 && direction := "W")
         (cos > 0 && direction := "E")
      }
   }
   if (distance <= step)
      nextPoint := point2
   else {
      stepX := Abs(X*step/distance)
      stepY := Abs(Y*step/distance)
      nextPoint := { x: point1.x + stepX*(X > 0 ? 1 : -1)
                   , y: point1.y + stepY*(Y > 0 ? 1 : -1) }
   }
   Return nextPoint
}
Last edited by teadrinker on 31 Aug 2019, 18:18, edited 2 times in total.
User avatar
boiler
Posts: 5219
Joined: 21 Dec 2014, 02:44

Re: Object Follow Mouse

31 Aug 2019, 09:22

teadrinker wrote:
31 Aug 2019, 09:17
IMO, it's wrong. :think:
What I see:

Image

Does 3 deg correspond to NE? I think it should be E.
Why is that wrong? If it's any amount north (even just 3 deg) of due east, it should be NE, especially since we want to put it on a direct line towards the mouse.
teadrinker
Posts: 1630
Joined: 29 Mar 2015, 09:41
Contact:

Re: Object Follow Mouse

31 Aug 2019, 09:29

boiler wrote: If it's any amount north of due east, it should be NE
Image

Why this we see E? Why not SE?

I suppose all directions (N, NE, E, SE ...) should take sectors of the same size.
User avatar
boiler
Posts: 5219
Joined: 21 Dec 2014, 02:44

Re: Object Follow Mouse

31 Aug 2019, 09:41

I don't know. TheDewd says it works exactly as he wants. Feel free to continue to work on it if you'd like.
teadrinker
Posts: 1630
Joined: 29 Mar 2015, 09:41
Contact:

Re: Object Follow Mouse

31 Aug 2019, 10:56

There is also a bug in your code. If you stop moving the cursor, the window sometimes can't stop.
https://imgur.com/a/TGLsS9Q
User avatar
boiler
Posts: 5219
Joined: 21 Dec 2014, 02:44

Re: Object Follow Mouse

31 Aug 2019, 11:46

You can call it a bug if you want. It's because it overshoots it as it moves 16 pixels at a time. That's easy to address. The point of it was to make it follow the mouse on a line which it does.
User avatar
TheDewd
Posts: 1391
Joined: 19 Dec 2013, 11:16
Location: USA

Re: Object Follow Mouse

04 Sep 2019, 16:10

teadrinker wrote:
31 Aug 2019, 09:17
One more my try:

Code: Select all

SetBatchLines, -1
CoordMode, Mouse
guiHomeX := 300, guiHomeY := 300
step := 30
period := 200

Gui, New, -Caption +ToolWindow +AlwaysOnTop +hwndhGui
Gui, Color, Red
Gui, Font, s16 q5, Calibri
Gui, Add, Text, x0 y2 w30 h30 Center, 0
Gui, Show, x%guiHomeX% y%guiHomeY% w30 h30
OnMessage( 0x201, Func("WM_LBUTTONDOWN").Bind(hGui, guiHomeX, guiHomeY, period, step) )

Esc:: ExitApp

WM_LBUTTONDOWN(hGui, guiHomeX, guiHomeY, period, step, wp, lp, msg, hwnd) {
   static toggle := true, timer, mode := []
   if (hGui = hwnd) {
      ( !timer && timer := Func("MoveGui").Bind(mode, hGui, step, guiHomeX, guiHomeY) )
      if toggle := !toggle
         mode[1] := "goHome" ; move to guiHomeX, guiHomeY
      else {
         mode[1] := "follow" ; follow cursor
         SetTimer, % timer, % period
      }
   }
}

MoveGui(mode, hGui, step, guiHomeX, guiHomeY) {
   static prevDirection
   if (mode[1] = "follow")
      MouseGetPos, mouseX, mouseY
   WinGetPos, guiX, guiY, guiW, guiH, ahk_id %hGui%
   guiX += guiW/2, guiY += guiH/2
   nextPoint := GetNextPoint( {x: guiX, y: guiY}
                            , mode[1] = "follow" ? {x: mouseX, y: mouseY} : {x: guiHomeX, y: guiHomeY}
                            , step, direction )
   if (direction != prevDirection) {
      GuiControl, %hGui%:, Static1, % direction
      prevDirection := direction
   }
   Gui, %hGui%: Show, % "NA x" nextPoint.x - guiW/2 " y" nextPoint.y - guiH/2
   if (mode[1] = "goHome" && nextPoint.x = guiHomeX && nextPoint.y = guiHomeY) {
      SetTimer,, Off
      GuiControl, %hGui%:, Static1, 0
   }
}

GetNextPoint(point1, point2, step, ByRef direction) {
   X := Floor(point2.x - point1.x)
   Y := Floor(point2.y - point1.y)
   distance := (X**2 + Y**2)**.5
   if ( X = 0 && Y = 0 )
      direction := 0
   else {
      sin := Y/distance
      cos := X/distance
      (sin < -0.365 && direction := "N") ; 0.365 is sin 22.5 (45/2) degree
      (sin >  0.365 && direction := "S")
      if direction {
         (cos < -0.365 && direction .= "W")
         (cos >  0.365 && direction .= "E")
      }
      else {
         (cos < 0 && direction := "W")
         (cos > 0 && direction := "E")
      }
   }
   if (distance <= step)
      nextPoint := point2
   else {
      stepX := Abs(X*step/distance)
      stepY := Abs(Y*step/distance)
      nextPoint := { x: point1.x + stepX*(X > 0 ? 1 : -1)
                   , y: point1.y + stepY*(Y > 0 ? 1 : -1) }
   }
   Return nextPoint
}
@teadrinker,

Your new code works very well! The only criticism I have is that it's hard for me to understand.

I don't consider myself a novice, however It's not easy for me modify.

I suppose that's my fault, not yours! :lol:

I modified the code to include the Neko cat images. Please try the attached script (with images) and tell me what you think. Would you have done anything different?
Attachments
Neko-teadrinker.zip
(17.6 KiB) Downloaded 15 times
Image Bulldozer - Sokoban inspired game from 1994 recreated in AutoHotkey.

Return to “Ask For Help”

Who is online

Users browsing this forum: Ben, Bing [Bot], kdservices, ManualColdSwitch, Ploaie, vsub and 144 guests