diff options
| -rw-r--r-- | .editorconfig | 4 | ||||
| -rw-r--r-- | CONTRIBUTING.md | 20 | ||||
| -rw-r--r-- | Makefile | 24 | ||||
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | bin/values.rb | 80 | ||||
| -rw-r--r-- | config/values.yml | 30 | ||||
| -rw-r--r-- | config/values/player.s | 28 | ||||
| -rw-r--r-- | src/player.s | 22 |
8 files changed, 193 insertions, 19 deletions
diff --git a/.editorconfig b/.editorconfig index c420a82..bd627cd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,5 +9,9 @@ end_of_line = lf [*.{s,S}] indent_style = space +[*.rb] +indent_size = 2 +indent_style = space + [Makefile] indent_style = tab diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c70602d..215034b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,10 @@ ## Why? -Do you want to fix an error you have found? Do you know a way to improve my -6502-fu or do you know a technique on NES/Famicom development that might help -here? I am open for discussion and welcome any help! +- You want to make a suggestion on something that could be improved. +- You want to report a bug, something that doesn't work as expected. +- You know of a sick 6502 assembly technique that could be applied. + +I am open for discussion and welcome any help! ## How? @@ -35,6 +37,18 @@ In order to test your changes, I'd go this way: 3. Run the ROM that was produced with an emulator of your choosing. Make sure that things run as expected. +### Customizing the build process + +You can pass the following arguments to `make`: + +- `CC65`: the compiler to use (defaults to `cl65`). +- `CCOPTS`: the options to use for the compiler (defaults to `--target nes`). +- `RUBY`: the ruby to use (defaults to `ruby`). + +Note that you can also set `DEBUG=1`, and with that you will pass sobre extra +debugging options, like telling `cl65` to also output a `out/labels.txt` file +with memory address on the symbols that have been evaluated. + ## Modifying assets I am using [NEXXT studio 3](https://frankengraphics.itch.io/nexxt) for managing @@ -7,12 +7,23 @@ else Q = endif +# NOTE: you can configure `CC65` and `CCOPTS` with the compiler and its options +# that you might require. Moreover, if you pass `DEBUG` to `make`, then an +# `out/labels.txt` file will be generated. CC65 ?= cl65 CCOPTS ?= --target nes ifeq "$(DEBUG)" "1" CCOPTS += -g -Ln out/labels.txt endif +# Ruby is used to generate the files on `config/values/`. If it can't be found, +# a warning will be echo'ed. +# +# NOTE: you can actually set RUBY as an argument to `make` if you want to pass +# something special to it. +RUBY ?= ruby +HAS_RUBY := $(shell command -v $(RUBY) >/dev/null 2>&1 && echo true || echo false) + .PHONY: all all: clean deps build @@ -25,10 +36,19 @@ clean: .PHONY: deps deps: - @which $(CC65) >/dev/null 2>/dev/null || (echo "ERROR: $(CC65) not found." && false) + @which $(CC65) >/dev/null 2>/dev/null || (echo "ERROR: '$(CC65)' not found." && false) + +.PHONY: gen-values +gen-values: +ifeq ($(HAS_RUBY),true) + $(E) " GEN config/values" + $(Q) ruby bin/values.rb +else + @(Q) echo "WARNING: '$(RUBY)' not found; files under 'config/values/' will not be generated." +endif .PHONY: build -build: build-full build-partial build-pal +build: gen-values build-full build-partial build-pal .PHONY: build-full build-full: @@ -1,7 +1,9 @@ This is a port to the NES/Famicom of the renown [jetpac](https://en.wikipedia.org/wiki/Jetpac) game from Ultimate Play the Game. You can find a ROM to play the game in the [releases -page](https://github.com/mssola/jetpac.nes/releases). +page](https://github.com/mssola/jetpac.nes/releases). Read the +[CONTRIBUTING.md](./CONTRIBUTING.md) file if you want to make any changes, +report an issue or make a suggestion. ## The game diff --git a/bin/values.rb b/bin/values.rb new file mode 100644 index 0000000..3ff1578 --- /dev/null +++ b/bin/values.rb @@ -0,0 +1,80 @@ +#!/usr/bin/env ruby + +## +# Generate the different values on `config/values/*.s` by parsing the values on +# `config/values.yml`. The values on that file are agnostic to NTSC or PAL, and +# it's up to this script to produce constants which make sense for NTSC and PAL. +# That is, it's a way to ensure that both NTSC and PAL have the same experience +# (or at least as close as possible). + +## +# Parse the configuration. + +require 'yaml' + +config_path = File.join("#{File.dirname(__FILE__)}/..", 'config/') +config = YAML.safe_load_file(File.join(config_path, 'values.yml')) + +# Converts the given floating point value into a signed fixed point value in the +# 4.4 format. +def to_signed_fixed_point(value) + integer = value.to_i + raise "bad signed fixed point value" if integer > 7 || integer < -7 + integer &= 0b00001111 + + decimal = (value % 1) * 100 + decimal = ((decimal * 15) / 100.0).round & 0b00001111 + + (integer << 4) | decimal +end + +## +# Loop through the configuration and fetch values for NTSC and PAL. + +res = {} +config.each do |model, properties| + res[model] ||= { ntsc: {}, pal: {} } + + properties.each do |name, ntsc| + name = name.upcase + pal = (ntsc * 6) / 5.0 + + res[model][:ntsc][name] = to_signed_fixed_point(ntsc) + res[model][:pal][name] = to_signed_fixed_point(pal) + end +end + +## +# Generate each model as expected. + +def to_hex(value) + hex = value.to_s(16).upcase + + if hex.size == 1 + "$0#{hex}" + else + "$#{hex}" + end +end + +def values_to_asm(values) + contents = "" + values.each { |k, v| contents << " #{k} = #{to_hex(v)}\n" } + contents.rstrip +end + +res.each do |model, formats| + path = File.join(config_path, "values/#{model}.s") + contents = <<HERE +;; This file has been automatically generated via bin/values.rb. +;; DO NOT MODIFY this file directly: check config/values.yml instead. + +.ifdef PAL +#{values_to_asm(formats[:pal])} +.else +#{values_to_asm(formats[:ntsc])} +.endif +HERE + + File.open(path, 'w') { |f| f.write(contents) } +end diff --git a/config/values.yml b/config/values.yml new file mode 100644 index 0000000..257282e --- /dev/null +++ b/config/values.yml @@ -0,0 +1,30 @@ +# Constants used throughout the game in a floating point format. The +# `bin/values.rb` script will make sure to transform these into 4.4 signed fixed +# point values that fit into a byte, as expected by the code from this game. +# +# Each file will have the name of the key from the first level (e.g. "player" +# will become "config/values/player.s"), and under the file each constant will +# be upper cased (e.g. "player.throttle_up" will become "THROTTLE_UP" inside of +# the "player.s" file). + +player: + # Gravity and initial velocity when throttling from the ground. + gravity: 2.50 + blast_off: -1.50 + + # Throttling. + throttle_up: -2.90 + throttle_left: -2.90 + throttle_right: 1.90 + + # Walking constant velocities. + walk_left: -1.80 + walk_right: 0.80 + + # Horizontal bounces + bounce_left: -2.53 + bounce_right: 1.53 + + # Vertical bounces + reduce_full_speed: 1.00 + reduce_mid_speed: 0.53 diff --git a/config/values/player.s b/config/values/player.s new file mode 100644 index 0000000..a27d10a --- /dev/null +++ b/config/values/player.s @@ -0,0 +1,28 @@ +;; This file has been automatically generated via bin/values.rb. +;; DO NOT MODIFY this file directly: check config/values.yml instead. + +.ifdef PAL + GRAVITY = $30 + BLAST_OFF = $F3 + THROTTLE_UP = $D8 + THROTTLE_LEFT = $D8 + THROTTLE_RIGHT = $24 + WALK_LEFT = $ED + WALK_RIGHT = $0E + BOUNCE_LEFT = $DE + BOUNCE_RIGHT = $1D + REDUCE_FULL_SPEED = $13 + REDUCE_MID_SPEED = $0A +.else + GRAVITY = $28 + BLAST_OFF = $F8 + THROTTLE_UP = $E2 + THROTTLE_LEFT = $E2 + THROTTLE_RIGHT = $1D + WALK_LEFT = $F3 + WALK_RIGHT = $0C + BOUNCE_LEFT = $E7 + BOUNCE_RIGHT = $18 + REDUCE_FULL_SPEED = $10 + REDUCE_MID_SPEED = $08 +.endif diff --git a/src/player.s b/src/player.s index 9cb73f3..ebf6dfb 100644 --- a/src/player.s +++ b/src/player.s @@ -62,19 +62,15 @@ INIT_Y_POSITION_LO = ((Background::GROUND_Y_COORD - PLAYER_HEIGHT) & $0F) << 4 INIT_Y_POSITION_HI = (Background::GROUND_Y_COORD - PLAYER_HEIGHT) >> 4 - ;; The initial position on the X axis is more or less at the center. + ;; The initial position on the X axis is right below the mid platform. INIT_X_POSITION_LO = $00 - INIT_X_POSITION_HI = $07 + INIT_X_POSITION_HI = $08 ;; Different acceleration/velocity constants. - GRAVITY = $28 - THROTTLE_UP = $D8 - THROTTLE_LEFT = $D8 - THROTTLE_RIGHT = $28 - BLAST_OFF = $F8 ; Initial velocity from ground. - WALK_LEFT = $F8 - WALK_RIGHT = $08 - REDUCE_FULL_SPEED = $10 ; Next velocity after hitting a ceiling at full speed. + ;; + ;; NOTE: automatically generated via `bin/values.rb`. Check the + ;; `config/values.yml` to understand the meaning of each constant. + .include "../config/values/player.s" zp_screen_y = $40 zp_position_y = $41 ; NOTE: 16-bit. @@ -553,7 +549,7 @@ lda #REDUCE_FULL_SPEED bne @correct_vertical_velocity @reduced_velocity: - lda #8 + lda #REDUCE_MID_SPEED @correct_vertical_velocity: sta zp_velocity_y rts @@ -639,7 +635,7 @@ ;; game did. sec sbc #PLAYER_WIDTH - ldx #$E8 + ldx #BOUNCE_LEFT bne @horizontal_eject @left_collision: @@ -647,7 +643,7 @@ ;; need to add the tile width to it. clc adc #8 - ldx #$18 + ldx #BOUNCE_RIGHT @horizontal_eject: ;; The screen coordinate has been computed into the `a` register, and |
