Sprite Cutscenes In 4.5.6

Bucket Mouse

Active member
Today we're gonna learn how to make cutscenes with sprites acting them out (as opposed to Ninja Gaiden-style cinema screens, which eat up a lot more memory). In some ways NESMaker 4.5.6 has made this easier; in other ways, a lot LOT harder.

The easy part is that we don't have to install custom ASM to shut off the player sprite or the HUD anymore. They are screen switches in the Adventure module, so we just turn them on.

The hard part is everything else. Currently all sprites vanish from the screen whenever a text box appears. This is not good; we need to see our actors! In NESMaker 4.1 turning the sprites on was as simple as altering one byte in the ASM. But now we need all this....

The first step is to go to Project Settings > User Variables and make a new variable called spritePause. Next, find doHandleObjects.asm (do a search) and make these alterations. Comment out the first four lines, like I've shown below, because they are the very thing that shut the sprites off.

Code:
doHandleObjects:

;	LDA gameStatusByte
;	AND #%00000001 ;;; this will skip object handling.
;	BEQ dontSkipObjectHandling
;		RTS

Next, scroll down and find the line that simply says "doActiveObject." It should be line 78 or thereabouts if you're using Notepad+. Post this code directly below it (thank Joseph Rossi for this bug fix):

Code:
	;;;; THE ABOVE HAS BEEN COMMENTED OUT TO ADD A SCRIPT FOR PAUSING VISIBLE SPRITES DURING TEXT
	LDA #$01
	CMP spritePause
	BNE doActiveObject2
	
	SwitchBank #$1C
LDA Object_status,x
AND #OBJECT_OBSERVES_DRAWING
BNE +
JMP ObjectDoesNotDraw
+
TXA
PHA
TYA
PHA
JSR doDrawSprites
PLA
TAY
PLA
TAX
JMP ObjectDoesNotDraw
	
	doActiveObject2:
		
	;;;; END OF ADDED MATERIAL

Now open doDrawBox.asm, and put this at the top:

Code:
doDrawBox:
	LDA #$01
	STA spritePause

Then scroll to the middle of doDrawBox and locate the part that has the "COMPLETELY DONE WITH BOX" comment. Below STA queueFlags, add this:

Code:
	;;;;;;;;;; ADDED MATERIAL FOR TEXT BOX SPRITE PAUSING
						
	LDA #$00
	STA spritePause
	
	;;;;;;;;;;; END OF TEXT BOX SPRITE PAUSE MATERIAL

Now your sprites won't disappear during text boxes AND they'll stop moving, including your player sprite. Their animations will still be active, though. So if you don't want that, there's one more step....

Locate doUpdateSpriteTimer.asm and paste this at the top:

Code:
		LDA #$01
	CMP spritePause
	BNE +
	JMP animationTimerNotFinished
	+

That'll do it. The sprites are ready, but the text isn't. See the next post for what to do about that....
 

Bucket Mouse

Active member
NOTE FROM 2021: This portion of the tutorial is out of date and can be skipped -- it was written to bypass the 256-character limit on text boxes, which is no longer a thing as of NESMaker Version 4.5. I'm keeping it here anyway in case we ever need this trick for something else

So the next thing we need to do is gain the ability to chain text boxes together. We had a routine for this in 4.1 that hijacked one of the unused menu items on NESMaker's text entry screen (we're referring to the drop-down menu that says "End Of Text Action.") If we open doDrawText.asm, find the "NotEndTrigger" line and do this below it...

Code:
           CMP #_ENDSHOP
    BNE notEndShop

notEndShop:
        CMP #_MORE
        BNE notMoreText
       
                movehere:

...we can now use the Open Shop menu item to go immediately to the next text box, bypassing the limit. HOWEVER the text this picks up will NOT be the next text you placed in your Text Groups for this screen. They'll appear in the order in which you entered them. Though this method technically works, it's a royal pain to deal with, as you'll have to constantly cut, paste and rearrange your text entries to make sure they all run in the proper order.

We spent a sleepless night on the Discord trying to figure out how to involve the Text Groups again, like in 4.1. There were a lot of aggravating dead ends and I wasn't sure it was even possible anymore, but our hero Kasumi figured it out as the sun rose. Here is his script, which goes in the same place...

EDIT: I've added a line that includes a variable from a later script, to prevent a bug with that one. So the variable textaction is now required, unless you don't plan to use the Text Action script and cut it out.

Code:
           CMP #_ENDSHOP
    BNE notEndShop
   
    INC textaction
   
    TYA ;Y is used later...
    PHA
   
    SwitchBank #$16

   LDY screenText
    INY
    STY screenText
   
    LDA stringGroupPointer,y
    TAY
   
    LDA TextStrings00_Lo,y
    SEC
    SBC #1 ;Subtracting 1 from the pointer because moretext adds 1
    STA textPointer
   
    LDA TextStrings00_Hi,y
    SBC #0 ;subtracting one from the high byte
    STA textPointer+1
   
    PLA
    TAY
   
    ReturnBank
   
    JMP movehere

notEndShop:
        CMP #_MORE
        BNE notMoreText
       
                movehere:

Now we can put whatever text boxes we want on any screen we want, in any order we want, by simply using the Text Groups. Ahh, so convenient!

This is very useful in itself, but we have one more task to perform. There was a custom AI script in 4.1 called TextAction that allowed NPCs to call up text at will, and used the Text Group to change the text every time (so you didn't need four different AI scripts taking up space). I believe it should now be possible to modify this slightly to work as the new TextAction. Further developments to come...
 
Last edited:

Bucket Mouse

Active member
When I try a variant of this as a monster AI, the first text box pops up, but it freezes there. And the problem isn't the script -- when I use something as simple as THIS, the text box also freezes:

Code:
    LDA npcTrigger
    ORA #%00000001
    STA npcTrigger

    DrawBox #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH , #BOX_1_HEIGHT, #TEXT_NPC, screenText; npc_text

So the AI system just isn't taking the native text script right, and I don't know why. Any ideas?
 

Bucket Mouse

Active member
Okay....I've solved the issue from the previous post. The Autotext tile and monster AI text isn't working because the input script for text, drawNPCText, is only written for talking to NPCs. There's a ton of code in there about sprites and collision boxes that it doesn't know what to do with if none are involved. So we have to tell the script to skip all that extra stuff if the text box is already on.

Add this to the beginning of drawNPCText, and it will fix the issue.

Code:
        LDA #$01
CMP spritePause
BNE textActionIsOff
JMP dontSkipNPCtext
textActionIsOff:

Now all we need is a version of Text Action that works in 4.5.6, and we're ready to make cutscenes. And what do you know, I've got it right here (requires the textaction variable)

Code:
	LDA textaction
	CMP #$03
	BNE +
	JMP textFour
	+
	LDA textaction
	CMP #$02
	BNE +
	JMP textThree
	+
	LDA textaction
	CMP #$01
	BNE +
	JMP textTwo
	+
	LDA textaction
	CMP #$00
	BNE +
	JMP textOne
	+
	RTS
	
	textFour:
	DrawBox #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH , #BOX_1_HEIGHT, #TEXT_NPC, screenText+3
	LDA #$00
 	STA textaction
	RTS
	
	textThree:
	DrawBox #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH , #BOX_1_HEIGHT, #TEXT_NPC, screenText+2
	
	LDA #$03
	STA textaction
	
	RTS
	
	textTwo:
	DrawBox #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH , #BOX_1_HEIGHT, #TEXT_NPC, screenText+1
	
	LDA #$02
	STA textaction
	RTS
	
	textOne:
	
	DrawBox #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH , #BOX_1_HEIGHT, #TEXT_NPC, screenText
	
	LDA #$01
  	STA textaction
	RTS

The reason this took so long is because I thought it couldn't possibly be this simple. The text group for a screen is loaded into memory as screenText, screenText+1, screenText+2 and screenText+3. That's not the kind of variable I'm familiar with, and I thought using it in any other context would cause an error.

Text Action also requires that the variable be reset every time a new screen loads. So do a search for every WarpToScreen script in BASE 4.5 and add these two lines to the beginning:

Code:
LDA #$00
    STA textaction ;; these two lines added for cutscenes
 

Pauldalyjr

Active member
You can also call specific lines of text like #000 through #255 if you want more than what that screen offers. Replace the screen text portion of the code with the text string you want to use
 

Bucket Mouse

Active member
Pauldalyjr said:
You can also call specific lines of text like #000 through #255 if you want more than what that screen offers. Replace the screen text portion of the code with the text string you want to use

Keep in mind though this script was written to be used over and over again. A script that only calls up a specific text will only ever be good for THAT specific text.
 

Bucket Mouse

Active member
Whoops -- I forgot something important! We need to turn off the player sprite on cutscene screens.

So let's go to Project Settings > Project Labels and change Screen Flag 2 to say "Hide Player." When we finish with this, we'll be able to check that box on every screen we want the player to disappear on and it will happen, just like Hide Hud and Hide Sprites.

Next, while we're here, let's go to User Variables and make two variables called "playerFlag" and "hidingFlag"

Finally, we'll search for ExtraScreenLoadData in the folder of the module we're using (I'm using Adventure, haven't tested any of this with anything else). Below the existing conditons and above Get Trigger, we'll paste this:

Code:
;; UNDER WHAT CONDITIONS SHOULD WE HIDE THE PLAYER?
	LDA ScreenFlags00
	AND #%00100000
	BEQ doNotErasePlayerSprite
	;; if the screen flag is on, and the player sprite was already
	;; destroyed on the previous screen, it will destroy the first
	;; monster sprite as well....unless we do this
	LDA #$01  
			CMP hidingFlag
		BNE playerFlagStillOn
		JMP doneWithExtraScreenCheckForPlayer
		playerFlagStillOn:
		LDX player1_object
		DestroyObject
		LDA #$01
		STA playerFlag
		STA hidingFlag
	JMP doneWithExtraScreenCheckForPlayer
	doNotErasePlayerSprite:
		LDA #$01
		CMP playerFlag
		BNE doneWithExtraScreenCheckForPlayer
		DEC playerFlag
		DEC hidingFlag
		CreateObject #START_POSITION_PIX_X, #START_POSITION_PIX_Y, player1_object, #$00
	doneWithExtraScreenCheckForPlayer:

So what is playerFlag for? Well, for some stupid reason the player sprite won't redraw when we warp to the next screen, even if the Hide Player check is off. This wasn't the case in 4.1 but it's the case now.
So we have to command the program to redraw the player, BUT ONLY ONCE -- only when it returns from a cutscene screen. If we don't, it will draw an extra player sprite on every screen forward.

There's a defect with this one I don't understand....you must position the player warp-in tile and the tile Monster 1 appears on at the same square, because Monster 1 will appear at the warp-in coordinates for some reason. No idea why, but you only have to do this with the first screen.
 

PortableAnswers

New member
Bucket Mouse said:
Whoops -- I forgot something important! We need to turn off the player sprite on cutscene screens.

So let's go to Project Settings > Project Labels and change Screen Flag 2 to say "Hide Player." When we finish with this, we'll be able to check that box on every screen we want the player to disappear on and it will happen, just like Hide Hud and Hide Sprites.

Next, while we're here, let's go to User Variables and make a variable called "playerFlag."

Finally, we'll search for ExtraScreenLoadData in the folder of the module we're using (I'm using Adventure, haven't tested any of this with anything else). Below the existing conditons and above Get Trigger, we'll paste this:

Code:
	;; UNDER WHAT CONDITIONS SHOULD WE HIDE THE PLAYER?
	LDA ScreenFlags00
	AND #%00100000
	BEQ doNotErasePlayerSprite
		LDX player1_object
		DestroyObject
		INC playerFlag
	JMP doneWithExtraScreenCheckForPlayer
	doNotErasePlayerSprite:
		LDA #$01
		CMP playerFlag
		BNE doneWithExtraScreenCheckForPlayer
		DEC playerFlag
		CreateObject #START_POSITION_PIX_X, #START_POSITION_PIX_Y, player1_object, #$00
	doneWithExtraScreenCheckForPlayer:

So what is playerFlag for? Well, for some stupid reason the player sprite won't redraw when we warp to the next screen, even if the Hide Player check is off. This wasn't the case in 4.1 but it's the case now.
So we have to command the program to redraw the player, BUT ONLY ONCE -- only when it returns from a cutscene screen. If we don't, it will draw an extra player sprite on every screen forward.

I got this to work in the MetroVania Module by placing this code in extraScreenLoad_PlatformBase.asm for the Extra Screen Load script.
HOWEVER, I cannot get the player to reappear upon the game starting even with the playerFlag variable. I tried setting it to 0 and 1. No luck.
 

Bucket Mouse

Active member
I don't have any experience with the Metroidvania module, but it's possible on scrolling engines like this the starting point works a different way. It is CreateObject that generates the sprite, not PlayerFlag. PlayerFlag simply tells the script to run the CreateObject macro.
 

DarthAT

Member
Okay....I've solved the issue from the previous post. The Autotext tile and monster AI text isn't working because the input script for text, drawNPCText, is only written for talking to NPCs. There's a ton of code in there about sprites and collision boxes that it doesn't know what to do with if none are involved. So we have to tell the script to skip all that extra stuff if the text box is already on.

Add this to the beginning of drawNPCText, and it will fix the issue.

Code:
        LDA #$01
CMP spritePause
BNE textActionIsOff
JMP dontSkipNPCtext
textActionIsOff:

Now all we need is a version of Text Action that works in 4.5.6, and we're ready to make cutscenes. And what do you know, I've got it right here (requires the textaction variable)

Code:
    LDA textaction
    CMP #$03
    BNE +
    JMP textFour
    +
    LDA textaction
    CMP #$02
    BNE +
    JMP textThree
    +
    LDA textaction
    CMP #$01
    BNE +
    JMP textTwo
    +
    LDA textaction
    CMP #$00
    BNE +
    JMP textOne
    +
    RTS
   
    textFour:
    DrawBox #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH , #BOX_1_HEIGHT, #TEXT_NPC, screenText+3
    LDA #$00
     STA textaction
    RTS
   
    textThree:
    DrawBox #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH , #BOX_1_HEIGHT, #TEXT_NPC, screenText+2
   
    LDA #$03
    STA textaction
   
    RTS
   
    textTwo:
    DrawBox #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH , #BOX_1_HEIGHT, #TEXT_NPC, screenText+1
   
    LDA #$02
    STA textaction
    RTS
   
    textOne:
   
    DrawBox #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH , #BOX_1_HEIGHT, #TEXT_NPC, screenText
   
    LDA #$01
      STA textaction
    RTS

The reason this took so long is because I thought it couldn't possibly be this simple. The text group for a screen is loaded into memory as screenText, screenText+1, screenText+2 and screenText+3. That's not the kind of variable I'm familiar with, and I thought using it in any other context would cause an error.

Text Action also requires that the variable be reset every time a new screen loads. So do a search for every WarpToScreen script in BASE 4.5 and add these two lines to the beginning:

Code:
LDA #$00
    STA textaction ;; these two lines added for cutscenes

Where does the section of the code go?
LDA textaction
CMP #$03
BNE +
JMP textFour
+
LDA textaction
CMP #$02
BNE +
JMP textThree
+
LDA textaction
CMP #$01
BNE +
JMP textTwo
+
LDA textaction
CMP #$00
BNE +
JMP textOne
+
RTS

textFour:
DrawBox #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH , #BOX_1_HEIGHT, #TEXT_NPC, screenText+3
LDA #$00
STA textaction
RTS

textThree:
DrawBox #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH , #BOX_1_HEIGHT, #TEXT_NPC, screenText+2

LDA #$03
STA textaction

RTS

textTwo:
DrawBox #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH , #BOX_1_HEIGHT, #TEXT_NPC, screenText+1

LDA #$02
STA textaction
RTS

textOne:

DrawBox #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH , #BOX_1_HEIGHT, #TEXT_NPC, screenText

LDA #$01
STA textaction
RTS
 

Bucket Mouse

Active member
It's a monster AI. It is its own ASM script, which you assign to a slot in Action Steps / AI Behaviors in Project Settings.
 

Jonny

Well-known member
I'm trying to use this with a text tile. I can get to the point where my objects will draw but the actual text box won't and seems to crash.
Surely there's a simpler way to not have the drawbox asm stop the drawing of objects.
 

CutterCross

Active member
I'm trying to use this with a text tile. I can get to the point where my objects will draw but the actual text box won't and seems to crash.
Surely there's a simpler way to not have the drawbox asm stop the drawing of objects.
It's far easier to simply replace the first RTS in your doHandleObjects.asm with a JMP to JustDrawObject, similar to the non-textbox based pause script.
Code:
doHandleObjects:
    LDA gameStatusByte
    AND #%00000001 ;;; this will skip object handling.
    BEQ dontSkipObjectHandling
    JMP JustDrawObject

This'll keep all sprites drawn during a textbox and disables object handling for stuff like inputs and actions.
 

Jonny

Well-known member
It's far easier to simply replace the first RTS in your doHandleObjects.asm with a JMP to JustDrawObject, similar to the non-textbox based pause script.
Code:
doHandleObjects:
    LDA gameStatusByte
    AND #%00000001 ;;; this will skip object handling.
    BEQ dontSkipObjectHandling
    JMP JustDrawObject

This'll keep all sprites drawn during a textbox and disables object handling for stuff like inputs and actions.

Thank you. I didn't think to look back over your pause script. I'm going to go through doHandleObjects and try to comment it out into a way that I can understand better. Thats the only way I can begin to make sense of some stuff.

Yes! Works lovely. I can get on with some cutscenes now. Thanks again
 

Jonny

Well-known member
It's a monster AI. It is its own ASM script, which you assign to a slot in Action Steps / AI Behaviors in Project Settings.

Just started playing about with text recently. I can't seem to get this to work as a monster action properly. The closest I can get is, if End Action AND End Animation are both set to Advance I can get it to quickly loop though the different text but in this order (text 1, text 4, text 3, text 2, text 1) then loops 4-1. If I try doing anything with th monster actions like use the timer or loop the animation I just get text 1 then text 3 in a loop, like its adding 2 instead of one.

Is this intended to be used in this way? Or with a button press. Basically, I just want the text to appear, stay for a few seconds then load the next. Seems simple but I'm really struggling to get anything to work. Any ideas what I might be doing wrong?
 

Bucket Mouse

Active member
What exactly are you trying to do? Load all four texts in one box, or have them appear sequentially in between sprite actions? Because that first one isn't necessary anymore. I kinda messed up with the "chain text boxes together" part. I was under the belief that version 4.5 had a character limit of 256 letters, like version 4.1 did. There's actually no limit anymore, so you can completely ignore my second post.
 
Top Bottom