6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sat Nov 23, 2024 6:20 pm

All times are UTC




Post new topic Reply to topic  [ 10 posts ] 
Author Message
PostPosted: Mon Nov 18, 2019 10:38 pm 
Offline

Joined: Sun Oct 13, 2019 6:20 am
Posts: 36
Location: Pennsylvania, USA
I gotta say, its super fun trying something and failing again and again until that one magical moment when the code is finally lines up in a way that makes what you're trying to do actually kinda work.

After having that happen to me a few times while learning 6502 in the last couple months, I can totally see the appeal of this hobby.

After finally getting a handle on some of the basics of 6502, I decided that I want to tackle something a little more ambitious for my next project. My first attempt at a super crude physics engine for a 2nd platformer.

Nothing grand. Just a little white dot that can move back and forth, jump and land on a couple platforms.

If I can get three features working I'll consider it finished.

1. Collision Detection (I actually got that one working and I'm super proud of it, despite how crude and unoptimized it is since I can't figure out good ways to use the BIT opcode yet. So, I'm stuck using ham fisted Compare opcodes for now. I'll fix it later. I'm just happy that its working.)
2. Gravity (It actually works now but only independently from everything else. I'm still working out how attach it to the main game input loop without breaking everything lol)
3. Momentum (I haven't the foggiest idea how to pull this one off, but it'll be fun trying to find out.)

Once again, as with all my projects so far, this is designed to run on Nick Morgan's Easy 6502 virtual machine:
http://skilldrick.github.io/easy6502/

My collision subroutine is too much of an unoptimized hot mess to show right now, but here's my first working version of the gravity subroutine:
Code:
 ;initializing position and color and first spawn
LDX #$02 ;setting high and low byte of spawn
STX $02
LDX #$08
STX $01

LDA #$01 ;setting color

STA ($01),y ;spawning dot

gravity:
CLC
JSR delay ;calling slow down delay
LDA #$00 ;clearing last position
STA ($01),y
LDA $01 ;drop position 1 space
ADC #$20
STA $01
LDA #$01 ;redraw dot in new position
STA ($01),y
BCC gravity ;if carry flag not set, fall again
LDA #$00   ;else, increment high byte, then back to fall
STA ($01),y
INC $02
LDX $02
CPX #$06 ;check if high byte is in range of display memory
BNE gravity ;If Yes, continue falling
DEC $02     ;Else, Hit Floor and stop falling
LDA $01
SBC #$20
STA $01
LDA #$01
STA ($01),y
JMP end

delay:             ;slowing down the fall
LDX #$a0
delayloop:
DEX
BNE delayloop
RTS

end:
BRK


Top
 Profile  
Reply with quote  
PostPosted: Tue Nov 19, 2019 9:04 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10986
Location: England
Great! You must be well on your way to a bouncing ball - at least, a bouncing dot.


Top
 Profile  
Reply with quote  
PostPosted: Tue Nov 19, 2019 9:20 am 
Offline

Joined: Sun Oct 13, 2019 6:20 am
Posts: 36
Location: Pennsylvania, USA
Oh, I didn't even think of bouncing. Thats a good idea.

I was just going to use it to let the dot fall after it jumped or fell off a platform.


Top
 Profile  
Reply with quote  
PostPosted: Tue Nov 19, 2019 11:51 am 
Online

Joined: Mon Jan 19, 2004 12:49 pm
Posts: 987
Location: Potsdam, DE
EvilSandwich wrote:
Oh, I didn't even think of bouncing. Thats a good idea.

I was just going to use it to let the dot fall after it jumped or fell off a platform.


Sooner or later it will bounce off your foot! :shock:

Neil


Top
 Profile  
Reply with quote  
PostPosted: Tue Nov 26, 2019 3:32 pm 
Offline

Joined: Sun Oct 13, 2019 6:20 am
Posts: 36
Location: Pennsylvania, USA
Okay, I'm still figuring out the basics of the other subroutines, but in the meantime I coded a "framework" to seamlessly insert the collision and momentum subroutines later as I figure them out. Just to keep it all organized and save me the trouble later.

Code:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; initializing Physics test position, dot color and first spawn
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

LDX #$02    ;setting high and low byte of spawn
STX $02
LDX #$08
STX $01

LDA #$01    ;setting color

STA ($01),y   ;spawning dot


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Main Loop
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

mainloop:
JSR gravity
JSR applymomentum ;Empty for now
JSR checkcollision ;Empty for now
JSR readkeys      ;Empty for now
JMP mainloop

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Gravity
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

gravity:
LDX #$00   ;Clearing X register
CLC      ;Clearing Carry
JSR delay   ;Calling slow down delay
LDA #$00   ;Clearing last position
STA ($01),y
LDA $01      ;drop position 1 space
ADC #$20
STA $01
LDA #$01   ;redraw dot in new position
STA ($01),y
BCC returngrav   ;if carry flag not set, fall again
LDA #$00      ;else, increment high byte, then back to fall
STA ($01),y
INC $02
LDX $02
CPX #$06    ;check if high byte is in range of display memory
BNE returngrav    ;If Yes, continue falling
DEC $02        ;Else, Hit Floor and stop falling
LDA $01
SBC #$20
STA $01
LDA #$01
STA ($01),y
JMP end

returngrav:
RTS

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Placeholder Sub routines
;; To Be Populated Later
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

applymomentum:
RTS

checkcollision:
RTS

readkeys:
RTS

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Delay to allow human input and reaction time
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

delay:      ;slowing down process
LDX #$a0
delayloop:
DEX
BNE delayloop
RTS

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Halt Program
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

end:
BRK


How's it looking so far?


Top
 Profile  
Reply with quote  
PostPosted: Tue Nov 26, 2019 11:11 pm 
Offline
User avatar

Joined: Wed Mar 01, 2017 8:54 pm
Posts: 660
Location: North-Germany
Nice idea :)

I would take care that all sort of math routines you use will have a nearly constant evaluation time - regardless whether the values causes carries to higher bytes or not. This should help keeping the final movement smooth.

Then your "gravity" isn't gravity, it is just speed. Gravity would accelerate your ball over time causing larger increments in vertical direction.

Perhaps you can implement something like:

Code:
ball_speed_y = ball_speed_y + gravity; // apply constant force increases speed
ball_pos_y = ball_pos_y + ball_speed; // compute new position
if (ball_pos_y >= max_pos_y)
{ ball_pos_y = max_pos_y - (ball_pos_y - max_pos_y); // bounce back
  ball_speed = - ball_speed * resilience_of_floor; // just for fun ;)
}
else
{ delay(spend_time_similar_to_then_case);
}

If you implement gravity as a byte (you may play with your ball in various places, like earth, moon, jupiter....) and speed as word and ball_pos_y as triple byte or long (32 bit) this should help making movement more realistic. It will consume much calculation time, but later you can try to optimize things. A 24 or 32 bit long y-position can easily converted to your screen resolution by just using the top most bits.

X motion can be implemented as well. Usually there is no constant force => 'x_gravity' = 0. But bouncing back and forth may be helpful. That means you have to check whether ball_pos_x is >= max_pos_x when x_speed is > 0 OR whether ball_pos_x <= min_pos_x if x_speed is < 0.

HAVE PHUN !!

And keep us informed.

:)

edit (1):
Code:
if (ball_pos_y >= max_pos_y)
{ ball_pos_y = max_pos_y - (ball_pos_y - max_pos_y) * resilience_of_floor; // bounce back with resilience!
  ball_speed = - ball_speed * resilience_of_floor; // just for fun ;)
}

should be more precise if resilience_of_floor isn't 1.0


Last edited by GaBuZoMeu on Wed Nov 27, 2019 12:02 am, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Tue Nov 26, 2019 11:41 pm 
Offline
User avatar

Joined: Sat Dec 01, 2018 1:53 pm
Posts: 730
Location: Tokyo, Japan
So, as someone new to 6502 assembly myself, I have a few comments on/queries about your code. Take this all with a grain of salt: I am probably unaware of many common conventions in 6502 coding (but happy to hear about them!).

Two general comments, first.

I find using equates for zero page locations makes things a lot easier to read and understand. E.g., instead of having a comment "set high and low byte of spawn":

Code:
spawn   EQU $01             ; pointer to spawn point of object

        LDX #$02
        STX spawn+1
        LDX #$08
        STX spawn

It also of course makes these locations easier to move around later.

I mention "pointer" in the comment because that indicates to me that this is actually "reserving" two bytes of memory at that location. Actually, I'd normally do this explicitly, if this makes sense to you:

Code:
        ORG $0001
spawn   DS  2               ; pointer to spawn point of object


Second, except when writing for people who don't understand the instructions at all (usually programmers in other languages), comments saying only what the instructions do should be left out. E.g., `LDX #$00` is pretty clear already, "Clearing X register" doesn't add anything. Knowing why you're clearing the X register, on the other hand, would be useful.

Code:
gravity:
LDX #$00   ;Clearing X register
CLC      ;Clearing Carry
JSR delay   ;Calling slow down delay


So I don't see why you clear X at all here; it seems to be loaded with something else before it's ever used. Also, I don't see why you clear the carry here, is that for the later `ADC`? If so, it seems to me you could move it down to to just above the `ADC` so that readers don't have to check `delay` to make sure it's not doing anything that could affect the carry.

How are you testing this code?

_________________
Curt J. Sampson - github.com/0cjs


Top
 Profile  
Reply with quote  
PostPosted: Wed Nov 27, 2019 1:51 am 
Offline

Joined: Sun Oct 13, 2019 6:20 am
Posts: 36
Location: Pennsylvania, USA
Much of the issues you're seeing for the lack of definitions is due to the limits of the javascript assembler I'm using.

Equates are far better, but it handles equates fairly awkwardly since it's mostly designed as a teaching tool rather than a serious assembler. I've had to make compromises because of that unfortunately.

As for the position of the CLC and the LDX #$00, you seem to be absolutely right. The CLC is way better above the ADC opcode than in the beginning of the subroutine. And clearing the X register seems pointless in hindsight. I'll tweak the code with that in mind.

I'm testing the code here, due to its dirt simple memory map.

http://skilldrick.github.io/easy6502/

GaBuZoMeu wrote:
Nice idea :)

I would take care that all sort of math routines you use will have a nearly constant evaluation time - regardless whether the values causes carries to higher bytes or not. This should help keeping the final movement smooth.

Then your "gravity" isn't gravity, it is just speed. Gravity would accelerate your ball over time causing larger increments in vertical direction.

I just had an idea! Maybe I can alter the delay subroutine with a shorter and shorter delay the longer it falls to simulate the acceleration of gravity?

Edit:
Okay, instead of the delay being a fixed number, I set it to a value in zero page address $03.

Code:
delay:      ;slowing down process
LDX $03                    ;Load Delay value to X
LDA $03                    ;Load Delay value to A
SBC #$05                  ;Decrease Delay Value
STA $03                     ;Reload to New Delay Value to Memory
delayloop:                  ;Begin Delay
DEX
BNE delayloop
RTS


Viola! Acceleration of Gravity!


Top
 Profile  
Reply with quote  
PostPosted: Wed Nov 27, 2019 3:09 am 
Offline
User avatar

Joined: Sat Dec 01, 2018 1:53 pm
Posts: 730
Location: Tokyo, Japan
EvilSandwich wrote:
Much of the issues you're seeing for the lack of definitions is due to the limits of the javascript assembler I'm using.
...
I'm testing the code here, due to its dirt simple memory map.
http://skilldrick.github.io/easy6502/

Right. Well, that's a perfectly reasonable place to start out and play a bit, but I think once your code grows to a few dozen instructions you may find it a bit awkward. (I guess it depends in part on whether or not you're a software engineer; for me Git and unit test frameworks are not something to be learned but an essential tool to reduce effort for even the tiniest projects.) I threw together a small unit test system for 6502 code (unit tests written in Python and code run in the py65 emulator) in my 8bitdev repo, if that helps with any ideas. (The current audience for that code is experienced developers who know and can program in Python, however; it's got a ways to go before some productization can be done.)

An easy place to start would be to put together a script that assembles your code, generates a binary file, and then starts an emulator on it. VICE has a command line option that lets you just aim it at a binary file and it comes up running, which is pretty convenient. And the C64 is a pretty reasonable platform on which to play with this stuff: you can use the text screen as a very basic 40x24 "graphics" display, just writing the chars into screen RAM (and colours into color RAM if you like), more or less as you do now, and expand to doing bitmap graphics later. (Or even use the C64's sprites, if you like.) If you want a simpler machine, the VIC-20 or a PET are also options.

Quote:
And clearing the X register seems pointless in hindsight.

Ok. That particularly stood out to me because in a routine I was working on recently, I had `LDX #0` at the beginning for use as a constant value in various places throughout the routine (loading zero into A, storing 0 in memory locations, as the zero-offset for some indirect address references), so it's not like what you did never makes sense.

Quote:
Okay, instead of the delay being a fixed number, I set it to a value in zero page address $03.

So the more typical way of handling acceleration is to use a fixed delay and give each object a velocity. Games generally target a 60 Hz, 50 Hz or 30 Hz frame rate, and update each object on the screen once every 16.67, 20.00 or 33.33 ms. This typically requires you to keep the position with higher resolution than the screen so that four frame of an object moving at 0.7 pixels/frame turns into on-screen moves of [1, 0, 1, 1] (3 pixels over four frames) rather than 1 pixel per frame, which would make it move as fast as something with a velocity of 1.2 pixels/frame.

So you might want to start even now working on the basics of the typical game framework loop:
  1. Add new objects and remove dead objects from the list.
  2. Passes through the list updating any characteristics that need to be changed, e.g. a gravity pass that updates the velocity of any objects that are affected by gravity. Typically there are lots of separate little passes that each do a specific thing, operating only on the relevant objects. E.g., if the colours of players change based on health points remaining, there'd be a separate routine to do those updates.
  3. Wait until display of the previous frame has completed.
  4. The drawing pass which, for any object with a velocity > 0, removes the old image, updates the location, and draws the new image.
  5. Collision detection pass to see what's crashed into what, or should be bouncing off, or whatever. This might be before the drawing pass, depending.

_________________
Curt J. Sampson - github.com/0cjs


Top
 Profile  
Reply with quote  
PostPosted: Wed Nov 27, 2019 10:35 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10986
Location: England
It might be interesting to consider Peter Noyes' Dodo Playground - a simple hello world example here (Run to assemble and launch the simulator, Button A to run the application.)

For the questions about physics engines, just to add a little to what's been said:
- you're already storing position of things
- and the next step is to implement velocity, which is just a delta, something to add to position.
- and the next step is to implement acceleration, which is just a delta to velocity.

As noted, using pixel coordinates as integers isn't going to look or act smoothly. But multiplication is expensive, as is excess precision. I'd recommend just one byte of fractional value, to make two or three byte values for everything. If you ever need to multiply, see if instead you can just scale by shifting, or maybe add one scaled value to another for finer control: that's a 2 by n multiply and is cheaper than n x n.

Don't even consider floats! Or trig functions. You can always get by with lookups, and you need surprisingly few values to make something playable.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 10 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 39 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: