Pixel-exact movement?

The place for codemasters or beginners to talk about programming any language for the Spectrum.
Post Reply
FFoulkes
Microbot
Posts: 161
Joined: Thu Feb 07, 2019 2:42 pm

Pixel-exact movement?

Post by FFoulkes »

Hi,

watching the awesome Spectrum Show, I thought about creating a retro game again.
At the moment, I'm trying to recreate the jump of Miner Willy (Manic Miner).
I took a look at how a "physics" jump would be emulated (in Pygame, I post my code below).
Then I thought, I better should experiment on the (emulated) Spectrum (using C), to find out, how the jump works in Spectrum pixels.
In BASIC, you can define UDGs and use "PRINT AT" to put them on the screen. I can do that in C too.
But Miner Willy doesn't just move in characters, he moves pixel-wise.
How on earth is this done?
The screen data is not even stored from top left to down right, like you would plot, but it's stored in these lines, that are shown, when a SREEN$-image is loaded.
"zx_pxy2saddr(x, y)" gives me a memory address. But there, a whole byte is stored. I then could alter a bit in this byte and plot, what I want that way, but that would be slow.
When I poke my whole UDG-bytes into these addresses, the UDG is shown quickly, but is moved only character-wise.
I assume, that "zx_pxy2saddr(50, 50)" and "zx_pxy2saddr(51, 50)" may give me the same memory address, if pixel 50/50 and pixel 51/50 are still in the same character-field.
That's all quite confusing (and frustrating to be honest). Could anybody tell me please, how pixel-wise movement is realized on the Spectrum?

----
Pygame-code for throw (or jump) movement:

Code: Select all

#!/usr/bin/python
# coding: utf-8

import pygame
from pygame.locals import *
import math
import os

G  = 9.81
T  = 0
DT = 0.017

class Ball:

    def __init__(self, colour, position):
        self.surface = pygame.Surface((20, 20))
        pygame.draw.circle(self.surface, colour, (10, 10), 10)
        self.rect = self.surface.get_rect()
        self.x_prog = position[0]
        self.y_prog = position[1]
        self.rect.topleft = position

    def initThrow(self, velocity, angle):
        self.velocity  = velocity
        self.angle     = angle
        self.x_phys    = 0
        self.y_phys    = 0
        # The velocity is divided into its x and y-parts:
        self.vy = self.velocity * math.sin(math.radians(self.angle))
        self.vx = self.velocity * math.cos(math.radians(self.angle))
        self.physToProg()
        self.start = (self.x_prog, self.y_prog)
        self.rect.topleft = self.start

    def moveBall(self):
        self.vy     -= G * DT
        self.x_phys += self.vx * DT
        self.y_phys += self.vy * DT 
        self.physToProg()
        self.rect.topleft = (self.x_prog, self.y_prog)

    def physToProg(self):
        self.x_prog =  400 * self.x_phys + 200
        self.y_prog = -400 * self.y_phys + 300


def processEvents():
    pygame.event.pump()
    pressed = pygame.key.get_pressed()
    if pressed[K_q] or pressed[K_ESCAPE]:
        return "quit"
    return 0

BLACK = (0, 0, 0)
RED   = (255, 0, 0)
BLUE  = (0, 0, 255)

pygame.init()
os.environ['SDL_VIDEO_WINDOW_POS'] = "223, 39"
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption('Throw')
clock = pygame.time.Clock()
ball1 = Ball(RED, (100, 200))
vel = 3
ang = 60
ball1.initThrow(vel, ang)
ball2 = Ball(BLUE, (100, 200))
ball2.initThrow(vel, ang)
finishedtime = 0

while True:
    clock.tick(20)
    if processEvents() == "quit":
        break
    screen.fill(BLACK)

    if ball1.rect.y <= ball1.start[1]:
        ball1.moveBall()
        T += DT
    else:
        finishedtime += 1
        # clocktick 20:
        if finishedtime > 20:
            ball1.initThrow(vel, ang)
            T = 0
            finishedtime = 0

    screen.blit(ball1.surface, ball1.rect)
    screen.blit(ball2.surface, ball2.rect)
    pygame.display.flip()
Firefox

Re: Pixel-exact movement?

Post by Firefox »

Like this! :)

https://skoolkit.ca/disassemblies/jet_s ... 37974.html

(edit) That's the drawing routine, here are the movement routines:

https://skoolkit.ca/disassemblies/jet_s ... 36307.html
User avatar
Ast A. Moore
Rick Dangerous
Posts: 2641
Joined: Mon Nov 13, 2017 3:16 pm

Re: Pixel-exact movement?

Post by Ast A. Moore »

I’m not sure what sort of answer you’re looking for. Do you want a working example in assembly or just a general outline of the methods, not tied to any particular programming language? Since we’re talking about the Spectrum, and most Spectrum games (especially those that have smooth pixel-based movement) are written in assembly, it would make more sense to use that.

I won’t go into detail in this post, but I’ll give you the general idea of pixel-based lateral movement.

I think you already know that there’s no special-purpose video hardware in the Spectrum. What you see on the screen is basically raw data represented graphically. If we set the color attributes aside for a moment, any dot (pixel) on the screen is a binary one stored in a memory location, and every “blank” is a zero.

Since the Spectrum is an 8-bit machine, each memory location is represented by eight bits comprising one byte. Thus, numeric values are translated directly into pixels on the screen. Say, the decimal number 1 in binary is represented as 00000001. That is seven zeros followed by one. On the screen this would look like seven “blank” pixels followed by a single “black” pixel. Now, let’s multiply this number by two. We’ll get two, which in binary is 00000010. Multiply it by two again, and you get four, which is 00000100, and so on. Each time the one moves exactly one bit (position) to the left. This simple multiplication (shift) will create an illusion of a pixel moving to the left on the screen.

Similarly, dividing by two will create an illusion of a pixel moving to the right.

Now, there are two general approaches to “moving” a sprite on screen. One is to have a single image and shift it (by multiplying or dividing the values it consists of by two), depending on the sprite’s intended position inside a screen byte. You’ll need to make up to seven such shifts. This is pretty slow but very memory-efficient. The other method is essentially that of animation. You create several (say, eight) different sprite “frames,” each preshifted by one pixel and then choose to display the frame which corresponds to the offset within a byte.

The actual offset value is obtained by simply masking off the five highest bits of the horizontal coordinate. Say, you horizontal position is 67. Sixty-seven divided by 8 is 8 and the remainder is 3. So, you’ll be drawing your sprite in the seventh byte counting from the lefthand edge of the screen, and the sprite will be shifted three pixels to the right within that byte. Thus, you’ll either need to shift your single sprite image right three times (divide it by eight), or choose to display Frame 3 of your pre-shifted sprite.

In terms of BASIC, you still do your PRINT AT, but before you do that you shift your desired graphic a certain number of times. A small caveat is that your shifted graphic will inevitably spill over to the next cell, which you’ll PRINT AT an adjacent horizontal position.

I hope that sheds some light on how pixel-based horizontal movement is achieved. Vertical movement is a little bit more complicated and usually requires the use of a formula and a table of pre-calculated values (for speed).
Every man should plant a tree, build a house, and write a ZX Spectrum game.

Author of A Yankee in Iraq, a 50 fps shoot-’em-up—the first game to utilize the floating bus on the +2A/+3,
and zasm Z80 Assembler syntax highlighter.
andydansby
Microbot
Posts: 147
Joined: Fri Nov 24, 2017 5:09 pm
Location: Syracuse, NY, USA
Contact:

Re: Pixel-exact movement?

Post by andydansby »

If you are using Z88dk the C compiler and want to use Sprites (the pixel level movement you asked about), you will need a low level sprite engine. There are a few available.

I used FASE in my last game, which is written in C with the low level Sprite engine. My open source code is at

https://github.com/andydansby/FASE-FN-Balls

There is also SP1 made by the talented Mr Albright.

Andy
User avatar
MonkZy
Manic Miner
Posts: 279
Joined: Thu Feb 08, 2018 1:01 pm

Re: Pixel-exact movement?

Post by MonkZy »

Each address corresponds to an X and Y like this :

Code: Select all

High Byte                                                        |   Low Byte

                        |  Y7       Y6    |  Y2    Y1    Y0      |   Y5      Y4     Y3 |  X4     X3    X2   X1    X0 |     
0        1         0        0        0        0     0     0            0      0     0     0      0     0     0    0
                        |                 |                      |                     |
16384 to form                Screen           character                 character                  character
screen address               third            pixel row                    row                    x position
So adding 1 to the high byte moves down one pixel line within a character row

adding 32 to the low byte moves down one character row

adding 8 to the high byte moves down one screen third

...i think
User avatar
R-Tape
Site Admin
Posts: 6407
Joined: Thu Nov 09, 2017 11:46 am

Re: Pixel-exact movement?

Post by R-Tape »

It's assembler, but this thread might be worth a look. If you know asm, then the routines "nextlinedown", "yx2screen" and "getsprite" were the ephiphanies that helped me understand :)

Sorry if I've misunderstood what you're asking, but here's the principle in a nutshell:

Say this is your sprite:

Image

You can print vertically from any line (there are 192 of these), so vertical movement is easy and smooth, but horizontally you can only print per column (there are only 32), so a 16 x 16 sprite has to be printed as a 24 x 16 object. Therefore you need to preshift the sprite as 8 images for how it would move between columns (8 pixels apart), like this:

Image

And with deleting, redrawing and moving, it looks like this:

Image
User avatar
Lethargeek
Manic Miner
Posts: 743
Joined: Wed Dec 11, 2019 6:47 am

Re: Pixel-exact movement?

Post by Lethargeek »

R-Tape wrote: Sun Apr 26, 2020 1:02 am You can print vertically from any line (there are 192 of these), so vertical movement is easy and smooth, but horizontally you can only print per column (there are only 32), so a 16 x 16 sprite has to be printed as a 24 x 16 object. Therefore you need to preshift the sprite as 8 images for how it would move between columns (8 pixels apart), like this:
i don't think he "needs" to preshift, as there are well known on-the-fly methods:
- shift the whole sprite in a dedicated sprite buffer before each output
- shift the each pixel line inside registers (in a loop)
- different unrolled loops for each shift amount
- shift by lookup table
sorted roughly from slower and smaller up to faster and bigger ones (but still smaller after some number of sprites)
highrise
Manic Miner
Posts: 300
Joined: Fri Mar 20, 2020 11:29 pm

Re: Pixel-exact movement?

Post by highrise »

although this concerns AGDx, some of the principles are demonstrated in this video that you might find helpful.

https://www.youtube.com/watch?v=kYQHn11nIrQ
dfzx
Manic Miner
Posts: 682
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

Re: Pixel-exact movement?

Post by dfzx »

Hopefully you'll have a better idea of the concepts from the posts the other guys have made.

From the C perspective you could use SP1, which is a graphics library which does pixel perfect positioning for you. The start of my SP1 programming guide is here. It's a rather complex library which assumes a grounding in C coding for the Spectrum. If it's a bit overwhelming you should start at the beginning of the getting started guide, here.

I wrote a game which moves a character through a jump animation. My approach was to hold a table of height offsets which increase then decrease the y-coordinate of the character as he moves laterally. The table is here and you can see the y-offset being applied to the character's screen position here (specifically line 284). The sprite is placed on the screen using pixel perfect coordinates here.
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
FFoulkes
Microbot
Posts: 161
Joined: Thu Feb 07, 2019 2:42 pm

Re: Pixel-exact movement?

Post by FFoulkes »

Wow, I'm really impressed! Each of the answers tells me exactly, what I wanted to know. Time to experiment with pre-shifting and the different sprite engines and libraries (like FASE and SP1).
Thank you very much!
FFoulkes
Microbot
Posts: 161
Joined: Thu Feb 07, 2019 2:42 pm

Re: Pixel-exact movement?

Post by FFoulkes »

Well, this is not the way to go, and it's not what I was supposed to do. But I did it anyway (as I don't understand the rest yet):

Code: Select all

#include <arch/zx.h>
#include <math.h>

/* willy1.c
Compilation: 

zcc +zx -vn -lm -startup=0 -clib=sdcc_iy -zorg=32768 willy1.c -o willy1 -create-app */

#define PI 3.141592654
#define G 9.81
#define DT 0.02

typedef unsigned char byte;

byte willy[16] = {6, 62, 124, 52, 62, 60, 24, 60, 126, 126, 247, 251, 60, 118, 110, 119};

void clearWillyAt(byte x, byte y) {
    byte i;
    int *addr;
    for (i = 0; i < 16; i++) {
        addr = zx_pxy2saddr(x, y + i);
        *addr = 0;
    }
}

void plotWillyAt(byte x, byte y) {
    byte i;
    int *addr;
    for (i = 0; i < 16; i++) {
        addr = zx_pxy2saddr(x, y + i);
        *addr = willy[i];
    }
}

void blitWilly(byte x, byte y, int delaytime) {
    int u;
    plotWillyAt(x, y);
    for (u=0;u<=delaytime;u++) {
    }
    clearWillyAt(x, y);
}

float degreeToRadian(float degree)  {
    return degree / 180 * PI; 
}

float yPhysToYProg(float y_phys) {
    return -150 * y_phys + 80;
}

int main() {
    zx_cls(PAPER_WHITE);
    int x_prog = 120;
    float velocity = 3.0;
    float degree = 45.0;
    float vy;
    float y_phys;
    float y_prog;
    float y_start;
    int rounds;
    for (rounds = 0; rounds <= 10; rounds ++) {
        vy = velocity * sin(degreeToRadian(degree));
        y_phys   = 0.0;
        y_prog   = yPhysToYProg(y_phys);
        y_start  = y_prog;
        blitWilly(x_prog, (byte) y_prog, 30000);
        while(y_prog <= y_start) {
            vy       -= G * DT;
            y_phys   += vy * DT;
            y_prog    = yPhysToYProg(y_phys);
            blitWilly(x_prog, (byte) y_prog, 3000);
        }
        blitWilly(x_prog, (byte) y_prog, 30000);
    }
    return 0;
}
dfzx
Manic Miner
Posts: 682
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

Re: Pixel-exact movement?

Post by dfzx »

Well it all but redefines the word "inefficient" but at least it works. :lol:

How about pre-calculating those Y positions into a table of integers and using that in the animation loop?
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
FFoulkes
Microbot
Posts: 161
Joined: Thu Feb 07, 2019 2:42 pm

Re: Pixel-exact movement?

Post by FFoulkes »

dfzx wrote: Mon Apr 27, 2020 9:54 am Well it all but redefines the word "inefficient" but at least it works. :lol:

How about pre-calculating those Y positions into a table of integers and using that in the animation loop?
You're right, that was rather inefficient. :roll: But now I check, if the sprite was already drawn at the pixel position and only redraw it, if not. It actually looks quite close to the real thing now, and I may be able to use the pixel positions as a blueprint for the animation. If I just want to print out the values, I even can use standard gcc on the PC:

Code: Select all

#include <arch/zx.h>
#include <math.h>

/* willy1.c
Compilation: 

zcc +zx -vn -lm -startup=0 -clib=sdcc_iy -zorg=32768 willy1.c -o willy1 -create-app */

#define PI 3.141592654
#define G 9.81
#define DT 0.003

typedef unsigned char byte;

byte willy[16] = {6, 62, 124, 52, 62, 60, 24, 60, 126, 126, 247, 251, 60, 118, 110, 119};

void clearWillyAt(byte x, byte y) {
    byte i;
    int *addr;
    for (i = 0; i < 16; i++) {
        addr = zx_pxy2saddr(x, y + i);
        *addr = 0;
    }
}

void plotWillyAt(byte x, byte y) {
    byte i;
    int *addr;
    for (i = 0; i < 16; i++) {
        addr = zx_pxy2saddr(x, y + i);
        *addr = willy[i];
    }
}

float degreeToRadian(float degree)  {
    return degree / 180 * PI; 
}

byte yPhysToYProg(float y_phys) {
    float a = -150 * y_phys + 80;
    return (byte) a;
}

int main() {
    zx_cls(PAPER_WHITE);
    byte x_prog = 120;
    float velocity = 3.0;
    float degree = 45.0;
    float vy;
    float y_phys;
    float y_start;
    byte y_prog;
    byte y_previous;
    int i;
    byte jumps;
    for (jumps = 0; jumps <= 10; jumps ++) {
        vy = velocity * sin(degreeToRadian(degree));
        y_phys   = 0.0;
        y_prog   = yPhysToYProg(y_phys);
        y_start  = y_phys;
        y_previous = y_prog;
        plotWillyAt(x_prog, y_prog);
        while(y_phys >= y_start) {
            vy       -= G * DT;
            y_phys   += vy * DT;
            y_prog    = yPhysToYProg(y_phys);
            if (y_prog != y_previous) {
                clearWillyAt(x_prog, y_previous);
                plotWillyAt(x_prog, y_prog);
                y_previous = y_prog;
            }
        }
        for (i = 0; i <= 30000; i++) {
        }
    }
    return 0;
}
dfzx
Manic Miner
Posts: 682
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

Re: Pixel-exact movement?

Post by dfzx »

I presume you don't need to be told what those floats are doing for the poor Z80? :)
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
Post Reply