New Monster AI: Move towards player

Bucket Mouse

Active member
As a programming exercise, I decided to try to make a new monster AI that makes the monster move in the direction of the player at all times.

I tend to learn code tricks by reverse-engineering code that already exists. There's already a routine that moves something in the player's direction: the "Shoot Toward Player" action. So I followed the breadcrumbs. In Macros you have this:

Code:
MACRO MoveTowardsPoint arg0, arg1, arg2, arg3, arg4
	;; arg0 = point of origin, x
	;; arg1 = poinrt of origin y
	;; arg2 = point to move towards x
	;; arg3 = point to move towards y

In Zero Out Assets, it's referenced:
Code:
MoveTowardsPlayer:
	MoveTowardsPoint temp, temp1, temp2, temp3, #$01
	RTS

And that, in turn, is referenced in ShootTowardsPlayer.asm:

Code:
	LDX player1_object
	LDA Object_x_hi,x
	STA temp1
	LDA Object_y_hi,x
	STA temp3
	LDX tempz ;; restore newly created projectile object.

	JSR MoveTowardsPlayer

The code before that deals with drawing the projectile and the code afterward deals with its speed. However, it confuses me that Object_y is stored in temp3 given that the notes in the Macro say it should be stored in temp2. In fact, If we're reading the X and Y for the player object's position, those things should go in temp2 and temp3! What's really going on?

I think "TempZ" is a variable exclusive to the projectile. Here's the full ShootTowardsPlayer code if you think there's anything important in here I missed:

Code:
;; shoot towards player.

	TXA
	STA tempx
	
	;; get offset
    LDA Object_x_hi,x
	CLC
	ADC #$04 ;; arbitrary...would put an 8x8 proj in the center of a 16x16 object
    STA temp1
	CLC
	ADC #$04 ;; arbitrary...would put a 8x8 proj in the center of a 16x16 object
    LDA Object_y_hi,x
    STA temp2
    CreateObject temp1, temp2, #$0A, #$01 ;; maybe use a different state for ignore physics?
	LDA #$00
	STA Object_movement,x
	;; we will skip traditional movement 
	;; and use direct speed instead.
	;; will this cause deacceleration?
	;; what we need is a bit to "ignore physics engine" which will ignore acc/dec
	
	LDA Object_x_hi,x

	STA temp
	LDA Object_y_hi,x

	STA temp2
	TXA
	STA tempz ;; store the newly created object's x
	;; shoot at player one - if there are two players, will we randomize somhow?
	LDX player1_object
	LDA Object_x_hi,x
	STA temp1
	LDA Object_y_hi,x
	STA temp3
	LDX tempz ;; restore newly created projectile object.

	JSR MoveTowardsPlayer 
	LDX tempz

	LDA myHvel
	
	STA Object_h_speed_lo,x
	LDA #$00
	STA Object_h_speed_hi,x
	LDA myVvel
	STA Object_v_speed_lo,x
	LDA #$00
	STA Object_v_speed_hi,x
	
	
gotVAimSpeeds:	
	
	
	LDX tempx

So this, as near as I can figure, is the ASM that will make the enemy progress toward the player at the speed they choose:

Code:
	LDX Object_type,x
	LDA Object_x_hi,x
	STA temp
	LDA Object_y_hi,x
	STA temp1
	
	LDX player1_object
	LDA Object_x_hi,x
	STA temp2
	LDA Object_y_hi,x
	STA temp3
	
         JSR MoveTowardsPlayer

There's only one problem: The code for the other enemy AI scripts looks completely different! For example, "Move randomly in 8 directions" looks like this:

Code:
;;;; Choose out of 8 directions.
    JSR GetRandomDirection
	AND #%00000111
    TAY
    LDA DirectionMovementTable,y
    STA temp
    TYA ;; the 0-7 value for direction
    ORA temp
    STA Object_movement,x

Logically, what I have should work. Is this just a different way of writing such things? I don't know.

Another thing I don't know is how to import this so I can test it out! There are some empty spaces in Object Details / Actions tab that say AI_Behavior_09, AI_Behavior_10, etc. But nothing in the tool lets me assign new scripts to those empty spaces.

A little help would be appreciated. If we get this working, we all can use it.
 

chronosv2

New member
Saw your post on Facebook and I can direct you somewhat, though I'm still finishing work on my HUD script so I can't look at the code yet.
The key to your last issues is in Project Settings.
You set what those AI behaviors do in the Defines section.
You set what labels the AI behaviors have in the Project Labels section.
 

MistSonata

Moderator
So, there's something to keep in mind with the MoveTowardPlayer macro, and that's that the object you're moving it with needs to ignore physics (at least for the object state that you're moving it on). Other than that, I don't see why it wouldn't work.

Alternatively, if you're okay with it being less precise, you could try writing an AI script where it compares its own position to the player's and moves toward the player in one of 8 directions. Something like this:

Code:
     LDY player1_object ;;load player object into Y register
     ;; Note: if this is an ai script, X should already be loaded with the current object, so no need to worry about that.

     LDA #$00
     STA temp ;;this is just to zero out the variable in case there's something there leftover from another script

     LDA Object_x_hi,y
     CMP Object_x_hi,x
     BCC playerIsToMyLeft ;;BCC or "branch on carry clear" acts as a "is this value less than the value I'm comparing it to?" after a compare instruction 
     ;; If it didn't branch, that means the player is to my right

     LDA temp
     ORA #MOVE_RIGHT ;;This is a constant that can be found in constants.asm
      ;; If you want to know what ORA does and how it works here, I recommend looking up 6502 instructions and how they work
     STA temp
     JMP skiptoAboveBelowCheck

playerIsToMyLeft:
     BEQ skiptoAboveBelowCheck ;;since we only checked for "less than", we need to branch if both values are the same

     LDA temp
     ORA #MOVE_LEFT
     STA temp

skiptoAboveBelowCheck:

     LDA Object_y_hi,y
     CMP Object_y_hi,x
     BCC playerIsAboveMe

     LDA temp
     ORA #MOVE_DOWN
     STA temp
     JMP endMoveTowardPlayer

playerIsAboveMe:
     BEQ endMoveTowardPlayer

     LDA temp
     ORA #MOVE_UP
     STA temp

endMoveTowardPlayer:

     LDA temp ;; temp should now be loaded with the movement information we want, and we can now store it in this object's Object_movement variable, which will make it move.
     STA Object_movement,x

     ;; no RTS because it's an AI script, and they manage subroutines and returns already

I haven't tested this to see if it works, but it should at least give you a starting point. There are also a lot of things to consider, like how often an object executes this code, which is the difference between a monster blindly charging at you and a character that acts like a homing missile.
 

Bucket Mouse

Active member
MistSonata said:
So, there's something to keep in mind with the MoveTowardPlayer macro, and that's that the object you're moving it with needs to ignore physics (at least for the object state that you're moving it on). Other than that, I don't see why it wouldn't work.

Alternatively, if you're okay with it being less precise, you could try writing an AI script where it compares its own position to the player's and moves toward the player in one of 8 directions. Something like this:

Code:
     LDY player1_object ;;load player object into Y register
     ;; Note: if this is an ai script, X should already be loaded with the current object, so no need to worry about that.

     LDA #$00
     STA temp ;;this is just to zero out the variable in case there's something there leftover from another script

     LDA Object_x_hi,y
     CMP Object_x_hi,x
     BCC playerIsToMyLeft ;;BCC or "branch on carry clear" acts as a "is this value less than the value I'm comparing it to?" after a compare instruction 
     ;; If it didn't branch, that means the player is to my right

     LDA temp
     ORA #MOVE_RIGHT ;;This is a constant that can be found in constants.asm
      ;; If you want to know what ORA does and how it works here, I recommend looking up 6502 instructions and how they work
     STA temp
     JMP skiptoAboveBelowCheck

playerIsToMyLeft:
     BEQ skiptoAboveBelowCheck ;;since we only checked for "less than", we need to branch if both values are the same

     LDA temp
     ORA #MOVE_LEFT
     STA temp

skiptoAboveBelowCheck:

     LDA Object_y_hi,y
     CMP Object_y_hi,x
     BCC playerIsAboveMe

     LDA temp
     ORA #MOVE_DOWN
     STA temp
     JMP endMoveTowardPlayer

playerIsAboveMe:
     BEQ endMoveTowardPlayer

     LDA temp
     ORA #MOVE_UP
     STA temp

endMoveTowardPlayer:

     LDA temp ;; temp should now be loaded with the movement information we want, and we can now store it in this object's Object_movement variable, which will make it move.
     STA Object_movement,x

     ;; no RTS because it's an AI script, and they manage subroutines and returns already

I haven't tested this to see if it works, but it should at least give you a starting point. There are also a lot of things to consider, like how often an object executes this code, which is the difference between a monster blindly charging at you and a character that acts like a homing missile.

Thanks to Chronos' help I was able to test both the scripts. Mine didn't work, but yours did.

Now I need to understand exactly why it worked so I can gain that knowledge for future experiments. So two questions:

1) You load all these things into Temp....I thought you could only load one value at a time into the Accumulator.

2) I looked up ORA.....there is some product out there called Oracle that makes finding the assembly definition of ORA on Google incredibly difficult. I could only track down one page that had the correct information I was looking for.

Logical Instructions
====================

AND - AND memory with accumulator
ORA - OR memory with Accumulator
EOR - Exclusive-OR memory with Accumulator

These instructions perform a bitwise binary operation according to the
tables given above. They set the Z flag if the net result is zero and
set the N flag if bit 7 of the result is set.

It still isn't clear to me what this does.
 

MistSonata

Moderator
Bucket Mouse said:
1) You load all these things into Temp....I thought you could only load one value at a time into the Accumulator.

2) I looked up ORA.....there is some product out there called Oracle that makes finding the assembly definition of ORA on Google incredibly difficult. I could only track down one page that had the correct information I was looking for.

Logical Instructions
====================

AND - AND memory with accumulator
ORA - OR memory with Accumulator
EOR - Exclusive-OR memory with Accumulator

These instructions perform a bitwise binary operation according to the
tables given above. They set the Z flag if the net result is zero and
set the N flag if bit 7 of the result is set.

It still isn't clear to me what this does.

ORA stands for OR A, basically a bitwise OR operation with the Accumulator, and what that means is that it takes a look at the binary bits of two numbers and flips each bit to 1 if the bit in the slot of either of those two numbers is a 1. Kind of like an OR gate in electronics, if you're familiar with that.

In the case of the code I posted, this loads the initial value of temp into the accumilator (which I set to #$00, which is #%00000000 in binary) and performs an OR operation with the #MOVE_LEFT constant (which is #%10000000 if you look it up in constants.asm) so...

Code:
   0000 0000
OR 1000 0000
____________
=  1000 0000

Then that value gets stored into temp, so that later I can ORA a second direction, let's just assume it's #MOVE_DOWN for this one, you'll see why this works in a moment.

Code:
   1000 0000
OR 0011 0000
____________
=  1011 0000

Hopefully that's a little bit clearer as to how the ORA operation works, but now for your first question. If you look at the constants.asm file, you'll notice that the binary number we ended up with is the same as MOVE_LEFT_DOWN, and we got that by ORA-ing MOVE_LEFT with MOVE_DOWN, this is by design. The movement variables use separate bits for horizontal and vertical movement, so if you OR a horizontal and vertical MOVE constant together, they won't interfere with one another. Basically it's a clever shortcut for skipping a whole lot of extra code, as otherwise you would have to individually create branches for all 8 directions instead of 4.

If you're still confused about the OR operation, there's a video about it here: https://www.youtube.com/watch?v=vCqrpPL1Fwc

Also I'd recommend this doc if you haven't read it already. https://www.dwheeler.com/6502/oneelkruns/asm1step.html
 

Bucket Mouse

Active member
MistSonata said:
Kind of like an OR gate in electronics, if you're familiar with that.

I am not, but I suppose whoever came up with all this decades ago had to be. When I see OR I think of the BASIC OR:
If A=5 OR B=7 THEN GOTO 100

Does "AND" mean something different as well?

I also feel like you'd have to REALLY, REALLY know binary off the top of your head to use these.

Oh yeah, and I tried to establish a variable tonight, and I couldn't even do that. Someone else did it on another topic so I followed his example:

I put experiment .dsb 1 in SystemVariables

I put

LDA #$02
STA experiment


in InitLoads

Then when I tried to use the variable somewhere else, I was told "Nonexistent label" and the game wouldn't run. Is there anywhere else I'm supposed to put it?
 

MistSonata

Moderator
Bucket Mouse said:
MistSonata said:
Kind of like an OR gate in electronics, if you're familiar with that.

I am not, but I suppose whoever came up with all this decades ago had to be. When I see OR I think of the BASIC OR:
If A=5 OR B=7 THEN GOTO 100

Does "AND" mean something different as well?

I also feel like you'd have to REALLY, REALLY know binary off the top of your head to use these.

Oh yeah, and I tried to establish a variable tonight, and I couldn't even do that. Someone else did it on another topic so I followed his example:

I put experiment .dsb 1 in SystemVariables

I put

LDA #$02
STA experiment


in InitLoads

Then when I tried to use the variable somewhere else, I was told "Nonexistent label" and the game wouldn't run. Is there anywhere else I'm supposed to put it?

Try using the built in variable definer instead (not the one in the HUD and Boxes, the one in Project>Project Settings>User Variables). If that doesn't work, try making sure you're spelling the variable the same each time, it's case sensitive.

AND is the inverse of ORA. It will look at each bit, and will put a 1 in the result ONLY if there's a 1 there in both of those slots. So...

Code:
    0101 0011
AND 0111 0010
_____________
=   0101 0010

This operation is useful for separating only the bits you need for a certain function. A common use I tend to find is separating out gamepad bits for, say, determining which direction the player is pressing on the d-pad.

Code:
    0110 0001 ;; this is what the variable gamepad will return if the player is pressing Down, Left, and A buttons. (The bits are Right, Left, Down, Up, Select, Start, B, and A, in that order)
AND 1111 0000 ;; by using AND we can ignore the buttons other than the d-pad, that way we can do operations that read the directional input
_____________
=   0110 0000
 

SuperNatetendo

New member
Added something to the script for a sorta "natural" face direction. Just add this at the bottom of MistSonata's script.

Code:
;; TRYING TO FIND DIRECTION. SHOULD ALSO WORK AS A "PROXIMITY" SCRIPT IF MODIFIED FOR SOMETHING ELSE

dirFaceDown:

	LDA Object_y_hi,y
	SBC Object_y_hi,x
	CMP #$08  	;;This checks vertical proximity to player in decimal value
	BMI dirFaceUp   
	
	LDA Object_movement,x
	AND #%11111000
	ORA #$00
	ORA #FACE_DOWN
	STA Object_movement,x
	
dirFaceUp:

	LDA Object_y_hi,x
	SBC Object_y_hi,y
	CMP #$08
	BMI dirFaceRight
	
	LDA Object_movement,x
	AND #%11111000
	ORA #$00
	ORA #FACE_UP
	STA Object_movement,x

dirFaceRight:

	LDA Object_x_hi,y
	SBC Object_x_hi,x
	CMP #$10  	;;This checks horizontal proximity to player in decimal value
	BMI dirFaceLeft      
	
	LDA Object_movement,x
	AND #%11111000
	ORA #$00
	ORA #FACE_RIGHT
	STA Object_movement,x

dirFaceLeft:

	LDA Object_x_hi,x
	SBC Object_x_hi,y
	CMP #$10
	BMI endFindChaseDirection
	
	LDA Object_movement,x
	AND #%11111000
	ORA #$00
	ORA #FACE_LEFT
	STA Object_movement,x

endFindChaseDirection:

It might be a little buggy as I haven't like REALLY stress tested it, but it seems to work pretty well for me.
 
I copied/pasted both scripts into ai script 10 but i got an error when compiling.
Can someone tell me what I did stupidly wrong?

Edit I thought I found out what my problem was,
and instead replaced null but I still got an error.
 

dale_coop

Moderator
Staff member
In your HUD, you have used/made an Image variable?
It is not supported in this version of NESMaker. So you need to remove it.
 

dale_coop

Moderator
Staff member
You did assigned the image var to an element of the HUD ?
If you did, select the element you previously assigned it to, select type "2-Text" and check you have nothing written in "String" property.
 

dale_coop

Moderator
Staff member
Still the same error?
It’s not the bmp the problem, it’s your HUD. In NESMaker, HUD & Boxes, in the “HUD elements” tab.
Did you used a “image variable” ? If you did, you need to remove it.
 

digit2600

Member
I'm trying to get this to work in a platformer, but for some reason, while it mostly works, the direction facing is off when entering the screen from the right... basically, monsters will move towards the player, but the directions they face when moving right tends to be erratic.
 
Top Bottom