Tile Based Strategy Game Mechanics

Goaterby

Member
Got a silly proof of concept working. The only sprite / object involved is the cursor, which uses a simple script for each direction to add 16px so it always stays on grid, along with a script for selection and deselection of units. It also uses several variables I added to keep track of:

- whether or not a unit / building is selected
- the index of the unit selected
- the underlying tile collision data
- the underlying tile graphic data

On a very basic level it works pretty swell! Or at least it looks nice on paper. There are several big things that are confounding me on taking it further, so maybe people can add to this topic if they are interested / knowledgeable and want to see this sort of thing in module form in the far future:

1. An efficient method for storing and accessing underlying tile data such that it can be read but not written over. (the current method I use works, but is a bit of a cop out, as it is only storing the last known tile, which varies from position to position / unit to unit over time, and is not actually storing all the data properly). I am guessing there would need to be a separate table of level data that you simply pull from whenever you swap a tile back? How do you add such a thing? EDIT: I got around this by making all ground tiles the same index, and simply being creative with how they palette swap, such that you get four different textures (shaded from light to dark). All other tiles are solid / other collision types.

2. Checking for tile collisions / indices at any given moment/place. I am currently using built-in tile types reworked to do what I need them to, so collisions are handled with the tiles as normal in engine, but what if I wanted to, if I hit the B button for instance, check a tile to the right and do something if it finds a certain index (like attacking to the right if there is an enemy tile)? Having tried this in code based upon the available scripts, it never seems to work right. EDIT: I figured out how to use the basic 'GetTileAtPosition' subroutine, which can be used to this end. You have to load the x and y you want to check into the system variables 'tileX' and 'tileY' before running the subroutine. The Y register then holds the tile type, and if you load the A register with the 'collisionTable,y' data, the A register has the tile type! You can then just do things by comparing it / branching etc. based upon what you got.

3. A good place /method to properly loop through each existent "object" represented in tiles and alter their variables as needed. Each unit or building in theory only needs a few variables to function (health, state, position for checking against others). Having worked with object oriented programming languages, this is the part that is the toughest to wrap my tiny brain around. Maybe just a table of all possible units / buildings up to a limit that you just add / subtract from with each thing having an index value to look it up from? I don't rightly know.


Hope this isn't too tenuous of a thing to post in this section, but I figured some people might be interested.

lXlqYTF.gif
 

Goaterby

Member
WolfMerrik said:
Wow, that is really cool!

Thanks! Hopefully I can make something more functional with it despite my lack of 6502 knowledge. Adding code here for clarity if anyone wants to use this stuff:

EDIT: The following post contains obsolete code. Feel free to mess around with it, but this setup does not provide for robust collisions. It also uses up all the tile types in order to do basic redrawing of the tiles, and it doesn't even work right. Continue reading down to newer posts to find updates and changes to it.

VARIABLES
Add these to /SystemVariables.asm
Code:
unitselected        .dsb 1 ;whether or not a unit is selected
    
myundertile         .dsb 1 ;the tile index "underneath" units (the index it used to be)
myundertilecol     .dsb 1 ;the collision data "underneath units (collision data it used to hold)
    
movepoints         .dsb 1 ;used to determine how far each tile can be moved, not currently used
sunitindex           .dsb 1 ;tile index of unit
sunitcolindex       .dsb 1 ;collision index of unit

And then define them in /InitLoads.asm
Code:
LDA #$00
STA unitselected
STA myundertile
STA myundertilecol
STA sunitindex
STA sunitcolindex
    
LDA #$04
STA movepoints    ;again, unused at the moment, you can ignore it


CURSOR MOVEMENT(MainGame:pressRight:NULL)
Code:
;change current tile back to enviro tile and move!
LDA unitselected
CMP #$01                	   ;if a unit is selected, clear the current tile back to its original value
BNE skiptilechangeR     ;if not, just move the cursor as normal
    ChangeTileAtCollision myundertilecol, myundertile

LDX player1_object      ;load player1_object pointy bit into X register  
LDA Object_x_hi,x       ;nab its x value
CLC
ADC #$10                ;add 16 to it
STA Object_x_hi,x
RTS

;move without altering tiles (do the same as above minus tile change)
skiptilechangeR:
LDX player1_object
LDA Object_x_hi,x
CLC
ADC #$10
STA Object_x_hi,x
RTS
Please note: if you are moving LEFT or UP, you are actually going to be subtracting (SBC instruction) #$F to stay on a 16x16 grid, as it seems there is a 1px offset existent when adding/subtracting from the origin, which makes sense considering sprites are an even number of pixels wide...

DESELECT ALL (MainGame:pressB:NULL)
Code:
;deselect unit
LDA #$00
STA unitselected
    
;change cursor to default state
LDX player1_object
ChangeObjectState #$00, #$00 ;states for cursor merely defines its sprite, in this case its default is a pointer
    
RTS
;nothin:
This would probably be better served as a combo of A+B buttons held on a short timer or something, as an entire button does not need to be used up for this. It is valuable real estate for other commands!

UPDATE TILES (MainGame:HoldNone:NULL)
Code:
LDA unitselected
CMP #$01
BNE skipmytileupdate
    
    ;change tile collision data and index to currently selected unit data/index
    ChangeTileAtCollision sunitcolindex, sunitindex
    RTS
    
skipmytileupdate:
;no unit selected, don't update tiles
RTS
This one is a little trickier because it needs to run each cycle to check the state of the 'unitselected' variable. This might not be the most efficient way of doing it, but it seems to work okay (famous last words). Someone correct me if there is any better way of running something each cycle in which you can change tiles.


Now it gets a bit harder. We have to mess with the tile types so we can use their built-in functionality to our advantage:

We need to make each type of terrain its own collision type, and each unit / building its own collision type. Renaming each of them really helps navigate the interface, as Joe has shown in various tutorial videos. This seems excessive, but barring direct access to the tilemap data (which I still can't seem to get working, can anyone clear up how to properly use the GetTileAtPosition subroutine? Is it object based or something?), this will do. Also, even if we were to make a janky game with this system, 16 tile types is enough to fit varied terrain types and a few units and buildings for each side! Good enough for now.

Once we have renamed them, deciding which tile types will be which, we need to add collision code for each of the tiles! I will just do a unit and two terrain tiles for the sake of brevity.

UNIT TILE COLLISION
Code:
LDA Object_type,x
CMP #$00    ;check if cursor (aka object 0) is colliding with you
BNE cursornotcolliding
    ;check gamepad for A button if cursor is colliding
    LDA gamepad
    AND #%00000001 ;check A button
    BNE selectunit  
    ;;do nothin if no A button presses detected
    RTS

        
;A button was pressed so unit is selected! change current selection and collision indices        
selectunit:
LDA #$01
STA unitselected
LDA #$02
STA sunitindex
LDA #$07
STA sunitcolindex

;change cursor to selectionbox state
LDX player1_object
ChangeObjectState #$01, #$10
RTS

cursornotcolliding:
RTS
Note how here, since we are on top of a unit tile, we check to see if the A button is being pressed, as that means that the player wants to select this unit! You would only have this code in the tile types that have cursor interactivity, like buildings /units / maybe a point and click menu or something.


GRASS TILE COLLISION
Code:
LDA Object_type,x
CMP #$00    ;check if cursor is colliding with you
BNE cnotcolliding1
    LDA #$00
    STA myundertile ;store the tile index for later (the position in the tile graphic)
    
    LDA #$B             
    STA myundertilecol ;store the collision type for later! (the number on the tile type list, in hex, mind you)
    RTS
cnotcolliding1:
RTS

BUSH TILE COLLISION
Code:
;store the current tile index so it can be repainted later
LDA Object_type,x
CMP #$00    ;check if cursor is colliding with you
BNE cnotcolliding3
    LDA #$40 ;same as before, but we have changed the index to the bush/forest whatever tile
    STA myundertile
    
    LDA #$D
    STA myundertilecol ;same as before, but we have changed the collision data to the right value
    RTS
cnotcolliding3:
RTS

Again, big disclaimer here, this """storage""" of the tile data / collision data is just the last tile the cursor happened to be on, which can vary, and is not a long term solution for doing this properly. Sorry about that. I can't figure out yet how to put arrays of things in here / access them.

Also of note: because we are dealing with tiles in place of sprites, you need to get real REAL creative with palette usage, as until we are able to directly change palette data at runtime on a tile-by-tile basis, any tiles that units can move on need to be their palette as well or they will swap when they go onto it. This means that, given the gif in the first post, if I wanted an enemy of a different color, I would need to sacrifice one of the knights colors. This means you get one shared color for transparency, one color for outlines / backgrounds, and then two colors, one for each side.

What is cool about this limitation though is that we can use it for neat effects, because palette swaps occur whenever you move onto a tile with a different palette! You could essentially have baked lighting where each tile that is in the 'dark' puts everything down a shade, or completely black, which means you could hide units or even make a really barebones fog of war (with a bit of imagination I think). I will definitely be experimenting with this more. EDIT: I added an example below. I didn't have enough time to properly set up the tile redrawing for the new palette tiles, so it looks a bit odd, but you will get the idea as a proof of concept I think.



And that's about it! I will try to update if progress is made. Sorry if it is a tad lacking, but I hadn't yet seen anyone else trying to make such a thing so I figured I would give it a shot...

dhp9gkG.gif
 

dale_coop

Moderator
Staff member
It's quite impressive and interesting. I will keep an eye on your project, that is very instructive (a lot of good ideas).
 

Goaterby

Member
Thanks Dale! Glad I could be of service. Most of my knowledge has actually come from your postings in the first place! So thank you for all the good posts!

EDIT: sorry for the multiple posts. Just posting another gif as I got the tiles working properly with the lighting. I really like this effect.
 

WolfMerrik

New member
Wow, that lighting effect is super cool!
What an awesome idea for a project. With some tweaking, you could probably make a MOD to help make users make a whole new genre!

Great work!
 
Wow. that is seriously some cool shit!

that palette lighting effect could be really great for those making horror games
(maybe something like an animated tile that moves into the light, spawn a sprite version of the creature, then destroys itself back to the terrain tile?)
 

Goaterby

Member
WolfMerrik said:
Wow, that lighting effect is super cool!
What an awesome idea for a project. With some tweaking, you could probably make a MOD to help make users make a whole new genre!

Great work!

Thanks! That is the idea. Again, I am not well versed in 6502, but I will be trying to get some more mechanics working sooner rather than later. Working on buildings / conditional solid collisions today.

I am familiar with strategy game development as I do that on the side on other platforms, but the NES provides some intimidating hurdles for strategy games, especially RTS! It would be a dream if I could get a basic strategy MOD together and functional. Biggest roadblock right now is understanding basic access of the level data. If I can find a function or make a function that just returns a tile map index (no collision or other data needed) at any given x/y or loops to find it etc., we will be golden, but this might take me a while to figure out and/or understand.


functionalform said:
Wow. that is seriously some cool shit!

that palette lighting effect could be really great for those making horror games
(maybe something like an animated tile that moves into the light, spawn a sprite version of the creature, then destroys itself back to the terrain tile?)

Yes indeed! I have already laid out the groundwork for this but am still messing with it to get an efficient method of object control / virtual intelligence. As it stands it might just be easiest to use lighter weight generic objects (i.e. like a 'melee' object but modified a bit) and have them control each enemy individually by hopping around on the grid and redrawing where needed. This would greatly simplify combat, as you could make melee / projectile objects from player tile positions as normal and use the built in collision for variable changes / health reduction. I will update with any progress, though it might be slow due to some other work I have at the mo.


EDIT: I figured out basic tilemap collision checks by picking pieces out of the adventure game tile checks. See below:

BASIC GRID MOVE LEFT
Code:
LDX player1_object
;;;;;;;;;;;;;;;;
LDA unitselected        ;check if a unit is selected
CMP #$01
BNE normalmoveL         ;if a unit is not selected, ignore collisions and tile changes
;;;;;;;;;;;;;;;;
    ;CHECK COLLISION
        ;x to check
        LDA Object_x_hi,x       ;load x value of object
        CLC
        SBC #$F                 ;subtract 16 px
        STA tileX               ;store this as the xvalue of the tile to check
        
        ;y to check
        LDA Object_y_hi,x       ;load y value of object
        STA tileY               ;store this as the yvalue of the tile to check
        
        ;Check tile at x/y
        JSR GetTileAtPosition
        LDA collisionTable,y
        ;; y is now loaded with tile type, do you need to undo this? They did so in the adventure tile script.
        CMP #$01                ;solid tile type
        BNE normalmoveL
            ;collision!
            RTS
            
    normalmoveL:
    LDA Object_x_hi,x
    CLC
    SBC #$F ;how far is it moving? (15px in this case, as there seems to be an offset)
    STA Object_x_hi,x ;; store it into the new x
    RTS

And it works! You can populate the screen with solid tiles, and you can move over them as normal, but if you select a unit and are 'moving' around with them, then you cannot cross solid tiles.

While this is great, and means that buildings / obstacle interaction now works, it broke the tile updating... Maybe someone better at this can shed some light here:

Code:
LDA Object_x_hi,x
CLC
SBC #$F
STA Object_x_hi,x ;; store it into the new x
RTS

This moves an object 15 pixels to the left. Simple yes?

Code:
ChangeTileAtCollision myundertilecol, myundertile
LDA Object_x_hi,x
CLC
SBC #$F
STA Object_x_hi,x ;; store it into the new x
RTS

This code does NOT move the object, instead merely changing the tile 15px to the left of it to the values given. Why is this?
 

Goaterby

Member
Alrighty, I have collisions working and have made a workaround for weird tile drawing. In the post before this, I detailed that the ChangeTileAtCollision code works strange when combined with the movement code. Why this is I don't know. As an educated guess, I figured it was better to separate the move code from the tile change code in case it needed a cycle or something, or maybe the Y or X registers are weird or the routine requires accessing variables in such a way that we can't mess with the x/y the next line or something. Maybe someone can clear that up. What is important to us is that we figure out a way to make it work for now. It is not good coding practice to forge ahead if you don't totally understand why things don't work, but this is a silly prototype and I want to share my successes as well as my dismal failures / misunderstandings in hopes of it being a better learning process.

So how do we separate code that needs to be bunched together if we need to do it on a button press? The button HOLD script slot! So this way we will run the movement code / check for collisions, and if there are no collisions, we will change the tile we are sitting on to a ground tile. On the HOLD for this particular DPAD button, we will then check for collisions a second time, and then move the cursor and change the tile. Please note that this does not ensure an airtight movement/collision scheme. For proper grid collisions, you want to check the tile ahead first, move yourself there, then change the tile behind you to be empty. In the set up I have made here, whatever gap between the press and the hold being registered could represent a possible false positive check if some player / enemy whatever were to move on that period between the checks (if there are in fact cycles in which that can happen), which might lead to a unit being on top of another unit. This will work for most cases though, and is good enough for now.

To ensure that the code isn't run a bazillion times since it is being called each cycle the button is held, we set up a simple binary variable that acts as a sort of one-shot switch on it. If the variable is 1, it runs. At the end of the code the variable is set to 0. At this point, it will not run the code anymore until the same button is released. There are some other changes and I will detail them below with the code. I did this for each direction on the dpad. Note that the positioning and collision checks will be different for different directions. i.e. if you are checking left, you will be checking the 'object_x_hi,x' minus #$F (15px to the left of the center of our tile) instead of plus #$10 (16px to the right of the center of our tile).

EDIT: Updated as of 9/1/18, as I had not provided for the weird cases that come about when you held the dpad then rapidly moved over / around units with or without them selected. With the old code, it would constantly clone units / make them disappear etc. The new code is not flawless, but it is much better. It was helped by adding a variable for each direction ('lmok' in the left direction scripts below), which only allowed the hold code to be run if the button was pressed in the first place and met all the other qualifications. I have also added a boatload of subroutines to the usersubroutines script such that these scripts are much easier to read / work with. I will add a list of them in the future if there is interest / needed clarification, but most of them are really self explanatory, and only use existing built-in routines that I have covered.

COLLISION CHECK #1 (MainGame:pressDPADLEFT:NULL)
Code:
LDA unitselected        ;check if a unit is selected
CMP #$01
BNE normalmoveL         ;if a unit is not selected, ignore collisions and tile changes
;;;;;;;;;;;;;;;;
    ;CHECK COLLISION
    JSR CheckTileLeft
    CMP #$01
    BCC tilemoveL
        ;solid collision!
        RTS   
        
normalmoveL:
JSR MoveCursorLeft
RTS
    
tilemoveL:
JSR ClearTileAtPos
LDA #$01
STA lmok
RTS

COLLISION CHECK #2 + CURSOR MOVEMENT (MainGame:HoldDPADLEFT:NULL)
Code:
LDA lmok
CMP #$01
BNE stayputL

LDA cursormoveable
CMP #$01
BNE stayputL

    LDA unitselected 
    CMP #$01
    BNE dontcposupdateL
        ;CHECK COLLISION
        JSR CheckTileLeft
        CMP #$01
        BCC cursorupdateL
        ;solid collision!
            RTS   
            
            cursorupdateL:
            JSR MoveCursorLeft
        ;reset tile position variables
        JSR ResetTilePosAtObj
        ;change tile underneath
        ChangeTileAtCollision sunitcolindex, sunitindex        
        LDA #$00
        STA cursormoveable
        STA lmok
        RTS
    
        dontcposupdateL:
        RTS

        stayputL:
        RTS

Neat! So we just ran collision code again, and this time moved the cursor and then updated our tile to our unit tile. Notice how all of this was qualified with the 'lmok' as well as the 'cursormoveable' variable. I had to add these to the initloads and the system variables scripts mentioned in the first post, as with all the variables. Now, for the last script, we need to reset the cursormoveable variable. We need to set it up for each dpad direction release for it to properly reset:

RESET CURSOR MOBILITY (MainGame:LeftDPADRELEASE:NULL)
Code:
;reset cursor mobility
LDA #$01
STA cursormoveable
RTS

Cool! So now if we duplicate the first two scripts for each direction, making doubly sure to swap all collision checks / movements so they correspond to said direction, we have a functioning control scheme with solid and unit collisions!

In the future, we can add separate collision types for buildings and things that we want to interact with the cursor, like if we wanted a building to make a unit or research something etc. we want to be able to select it just like units.

EDIT: Added these by means of the tile collision types, which I had turned away from using, but I found that there is no other way to quickly/easily access the nametable data directly to look things up. I have added a gif below showing the new setup. The movement, while better, can still produce unit clones / delete units occasionally if you really hammer out a bunch of random directions quickly on the dpad. To fix this in the near future, I will be adding a timer that only allows controller input / movement if a timer has finished counting down, such that you can't spam directions.

SyKQxXe.gif
 

JackBurton

New member
Hello,

Looks like some great work has been done. I'm new to NESmaker and I am getting ready for a stab at my first game.

My idea involves a grid and background tile manipulation. I am not sure what I all need, but it would seem some of the leg work has already been done.

If it turns out to be useful, may I use / modify some of the code / logic you posted?
 
Top Bottom