From fcfc16a9413f4fc043811b063d4b09f68be205ac Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 31 Mar 2026 18:02:52 -0700
Subject: [PATCH] Re-add game area zoom mode on phones
This ends up being a much more dynamic experience, and more fun!
---
game/game.cpp | 180 ++++++++++++++++++++++++++++++++++++++++++++++--
game/game.h | 2 +
game/init.cpp | 4 +-
game/player.cpp | 43 +++++++++++-
game/player.h | 10 +++
5 files changed, 230 insertions(+), 9 deletions(-)
diff --git a/game/game.cpp b/game/game.cpp
index af22f871..ab9cb7cd 100644
--- a/game/game.cpp
+++ b/game/game.cpp
@@ -599,8 +599,28 @@ GamePanelDelegate::UpdateZoom()
SDL_GetRenderSafeArea(renderer, &rect);
SDL_SetRenderLogicalPresentation(renderer, saved_w, saved_h, saved_mode);
- // We can zoom if we're on a phone or tablet in landscape mode
- if ((IsPhone() || IsTablet()) && rect.w > rect.h) {
+ // We can zoom if we're on a phone in landscape mode and not local multiplayer
+ bool zoom = false;
+
+ if (IsPhone() && rect.w > rect.h) {
+ int i;
+
+ int local_players = 0;
+ OBJ_LOOP(i, MAX_PLAYERS) {
+ if (!gPlayers[i]->IsValid()) {
+ continue;
+ }
+
+ if (IS_LOCAL_CONTROL(gPlayers[i]->GetControlType())) {
+ ++local_players;
+ }
+ }
+ if (local_players == 1) {
+ zoom = true;
+ }
+ }
+
+ if (zoom) {
StartZoom(rect);
} else {
StopZoom();
@@ -610,6 +630,7 @@ GamePanelDelegate::UpdateZoom()
void
GamePanelDelegate::StartZoom(const SDL_Rect &rect)
{
+ SDL_Renderer *renderer = screen->GetRenderer();
float scale = (float)GAME_WIDTH / rect.w;
int x = (int)SDL_round(rect.x * scale);
int y = (int)SDL_round(rect.y * scale);
@@ -617,6 +638,10 @@ GamePanelDelegate::StartZoom(const SDL_Rect &rect)
ui->SetPosition(x, y);
ui->SetSize(GAME_WIDTH, height);
+ if (!m_texture) {
+ m_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_UNKNOWN, SDL_TEXTUREACCESS_TARGET, GAME_WIDTH, GAME_HEIGHT);
+ }
+
m_zoom = true;
}
@@ -626,26 +651,139 @@ GamePanelDelegate::StopZoom()
ui->SetPosition(0, 0);
ui->SetSize(GAME_WIDTH, GAME_HEIGHT);
+ if (m_texture) {
+ SDL_DestroyTexture(m_texture);
+ m_texture = nullptr;
+ }
+
m_zoom = false;
}
void
GamePanelDelegate::StartZoomedDrawing()
{
- screen->SetLogicalSize(GAME_WIDTH, GAME_HEIGHT);
+ SDL_Renderer *renderer = screen->GetRenderer();
+
+ // Don't clip
+ screen->GetClip(&m_savedClip);
+ SDL_Rect clip = m_savedClip;
+ clip.y = 0;
+ clip.x = 0;
+ clip.w = GAME_WIDTH;
+ clip.h = GAME_HEIGHT;
+ screen->ClipBlit(&clip);
+
+ SDL_SetRenderTarget(renderer, m_texture);
+ screen->Clear();
}
void
GamePanelDelegate::StopZoomedDrawing()
{
- screen->SetLogicalSize(ui->X() + ui->Width() + ui->X(), ui->Y() + ui->Height() + ui->Y());
+ SDL_Renderer *renderer = screen->GetRenderer();
+
+ SDL_SetRenderTarget(renderer, nullptr);
+ SDL_SetRenderLogicalPresentation(renderer, 0, 0, SDL_LOGICAL_PRESENTATION_DISABLED);
+
+ int w = 0, h = 0;
+ SDL_GetRenderOutputSize(renderer, &w, &h);
+
+ int cameraX, cameraY;
+ gPlayers[0]->GetCameraPos(&cameraX, &cameraY);
+ GetRenderCoordinates(cameraX, cameraY);
+ cameraX += (SPRITES_WIDTH / 2);
+ cameraY += (SPRITES_WIDTH / 2);
+
+ SDL_Rect src;
+ if (w > h) {
+ int visible_width = (GAME_WIDTH - (2 * SPRITES_WIDTH));
+ float scale = (float)visible_width / w;
+ src.w = visible_width;
+ src.h = (int)SDL_roundf(h * scale);
+ src.x = cameraX - src.w / 2;
+ src.y = cameraY - src.h / 2;
+ } else {
+ int visible_height = (GAME_HEIGHT - (2 * SPRITES_WIDTH));
+ float scale = (float)visible_height / h;
+ src.w = (int)SDL_roundf(w * scale);
+ src.h = visible_height;
+ src.x = cameraX - src.w / 2;
+ src.y = cameraY - src.h / 2;
+ }
+ float minu = (float)src.x / m_texture->w;
+ float minv = (float)src.y / m_texture->h;
+ float maxu = (float)(src.x + src.w) / m_texture->w;
+ float maxv = (float)(src.y + src.h) / m_texture->h;
+
+ SDL_FRect dst;
+ dst.x = 0.0f;
+ dst.y = 0.0f;
+ dst.w = (float)w;
+ dst.h = (float)h;
+
+ SDL_FColor color = { 1.0f, 1.0f, 1.0f, 1.0f };
+ SDL_Vertex verts[6];
+ SDL_Vertex *vert = verts;
+ /* 0 */
+ vert->position.x = dst.x;
+ vert->position.y = dst.y;
+ vert->color = color;
+ vert->tex_coord.x = minu;
+ vert->tex_coord.y = minv;
+ vert++;
+ /* 1 */
+ vert->position.x = dst.x + dst.w;
+ vert->position.y = dst.y;
+ vert->color = color;
+ vert->tex_coord.x = maxu;
+ vert->tex_coord.y = minv;
+ vert++;
+ /* 2 */
+ vert->position.x = dst.x + dst.w;
+ vert->position.y = dst.y + dst.h;
+ vert->color = color;
+ vert->tex_coord.x = maxu;
+ vert->tex_coord.y = maxv;
+ vert++;
+ /* 0 */
+ vert->position.x = dst.x;
+ vert->position.y = dst.y;
+ vert->color = color;
+ vert->tex_coord.x = minu;
+ vert->tex_coord.y = minv;
+ vert++;
+ /* 2 */
+ vert->position.x = dst.x + dst.w;
+ vert->position.y = dst.y + dst.h;
+ vert->color = color;
+ vert->tex_coord.x = maxu;
+ vert->tex_coord.y = maxv;
+ vert++;
+ /* 3 */
+ vert->position.x = dst.x;
+ vert->position.y = dst.y + dst.h;
+ vert->color = color;
+ vert->tex_coord.x = minu;
+ vert->tex_coord.y = maxv;
+ vert++;
+
+ SDL_SetRenderTextureAddressMode(renderer, SDL_TEXTURE_ADDRESS_WRAP, SDL_TEXTURE_ADDRESS_WRAP);
+ SDL_RenderGeometry(renderer, m_texture, verts, 6, NULL, 0);
+ SDL_SetRenderTextureAddressMode(renderer, SDL_TEXTURE_ADDRESS_AUTO, SDL_TEXTURE_ADDRESS_AUTO);
+
+ SDL_SetRenderLogicalPresentation(renderer, ui->X() + ui->Width() + ui->X(), ui->Y() + ui->Height() + ui->Y(), SDL_LOGICAL_PRESENTATION_LETTERBOX);
+
+ screen->ClipBlit(&m_savedClip);
}
void
GamePanelDelegate::DrawBorder()
{
- SDL_Rect rect;
+ if (m_zoom) {
+ return;
+ }
+ SDL_Rect rect;
screen->GetClip(&rect);
rect.x -= 1;
rect.y -= 1;
@@ -1353,4 +1491,36 @@ void RenderSprite(UITexture *sprite, int x, int y, int w, int h)
w = (int)(((float)w * gScrnRect.w) / GAME_WIDTH);
h = (int)(((float)h * gScrnRect.h) / GAME_HEIGHT);
screen->QueueBlit(sprite->Texture(), x, y, w, h, DOCLIP);
+
+ // Render the other sides of the sprite
+ if (x < 0) {
+ x += GAME_WIDTH;
+ screen->QueueBlit(sprite->Texture(), x, y, w, h, DOCLIP);
+ } else if ((x + w) > GAME_WIDTH) {
+ x -= GAME_WIDTH;
+ screen->QueueBlit(sprite->Texture(), x, y, w, h, DOCLIP);
+ }
+ if (y < 0) {
+ y += GAME_HEIGHT;
+ screen->QueueBlit(sprite->Texture(), x, y, w, h, DOCLIP);
+
+ if (x < 0) {
+ x += GAME_WIDTH;
+ screen->QueueBlit(sprite->Texture(), x, y, w, h, DOCLIP);
+ } else if ((x + w) > GAME_WIDTH) {
+ x -= GAME_WIDTH;
+ screen->QueueBlit(sprite->Texture(), x, y, w, h, DOCLIP);
+ }
+ } else if ((y + h) > GAME_HEIGHT) {
+ y -= GAME_HEIGHT;
+ screen->QueueBlit(sprite->Texture(), x, y, w, h, DOCLIP);
+
+ if (x < 0) {
+ x += GAME_WIDTH;
+ screen->QueueBlit(sprite->Texture(), x, y, w, h, DOCLIP);
+ } else if ((x + w) > GAME_WIDTH) {
+ x -= GAME_WIDTH;
+ screen->QueueBlit(sprite->Texture(), x, y, w, h, DOCLIP);
+ }
+ }
}
diff --git a/game/game.h b/game/game.h
index 4a9c87b9..0c1a36e8 100644
--- a/game/game.h
+++ b/game/game.h
@@ -104,6 +104,8 @@ class GamePanelDelegate : public UIPanelDelegate
} m_state;
bool m_zoom;
+ SDL_Texture *m_texture = nullptr;
+ SDL_Rect m_savedClip;
};
/* ----------------------------------------------------------------- */
diff --git a/game/init.cpp b/game/init.cpp
index cf2923fb..3c122f60 100644
--- a/game/init.cpp
+++ b/game/init.cpp
@@ -220,8 +220,8 @@ static void DrawLoadBar()
void SetStar(int which)
{
- int x = FastRandom(GAME_WIDTH - 2*SPRITES_WIDTH) + SPRITES_WIDTH;
- int y = FastRandom(GAME_HEIGHT - 2*SPRITES_WIDTH) + SPRITES_WIDTH;
+ int x = FastRandom(GAME_WIDTH);
+ int y = FastRandom(GAME_HEIGHT);
gTheStars[which]->xCoord = x;
gTheStars[which]->yCoord = y;
diff --git a/game/player.cpp b/game/player.cpp
index 78a17311..024cc411 100644
--- a/game/player.cpp
+++ b/game/player.cpp
@@ -138,6 +138,8 @@ Player::NewWave(void)
Shooting = 0;
WasShooting = 0;
Rotating = 0;
+ CameraX = x;
+ CameraY = y;
phase = 0;
OBJ_LOOP(i, numshots)
KillShot(i);
@@ -179,6 +181,8 @@ Player::NewShip(void)
phasetime = NO_PHASE_CHANGE;
Dead = 0;
Exploding = 0;
+ CameraX = x;
+ CameraY = y;
Set_TTL(-1);
if ( ! gGameInfo.IsDeathmatch() ) {
if (Lives > 0) {
@@ -396,10 +400,11 @@ Player::ShotHit(Rect *hitRect)
}
return(NULL);
}
+
int
Player::Move(int Freeze)
{
- int i;
+ int i, result;
/* Move and time out old shots */
#ifdef SERIOUS_DEBUG
@@ -456,6 +461,8 @@ printf("\n");
/* Check to see if we are dead... */
if ( Dead ) {
+ UpdateCamera();
+
if ( --Dead == 0 ) { // New Chance at Life!
if ( NewShip() < 0 ) {
/* Game Over */
@@ -549,7 +556,39 @@ printf("\n");
} else
WasShielded = 0;
}
- return(Object::Move(Freeze));
+
+ result = Object::Move(Freeze);
+
+ UpdateCamera();
+
+ return result;
+}
+
+void
+Player::UpdateCamera()
+{
+ if ( Dead ) {
+ // Pan the camera over to our new position
+ const float CAMERA_SPEED = (float)(6 << SPRITE_PRECISION);
+ float deltaX = (float)(x - CameraX);
+ float deltaY = (float)(y - CameraY);
+ float length = SDL_sqrtf(deltaX * deltaX + deltaY * deltaY);
+ float velocityX = (deltaX / length) * CAMERA_SPEED;
+ float velocityY = (deltaY / length) * CAMERA_SPEED;
+ if (SDL_fabs(velocityX) < SDL_fabs(deltaX)) {
+ CameraX += (int)SDL_truncf(velocityX);
+ } else {
+ CameraX = x;
+ }
+ if (SDL_fabs(velocityY) < SDL_fabs(deltaY)) {
+ CameraY += (int)SDL_truncf(velocityY);
+ } else {
+ CameraY = y;
+ }
+ } else {
+ CameraX = x;
+ CameraY = y;
+ }
}
Uint8
diff --git a/game/player.h b/game/player.h
index 1d6e287c..fefa4fe8 100644
--- a/game/player.h
+++ b/game/player.h
@@ -167,6 +167,11 @@ class Player : public Object {
}
bool CanGetAchievement();
+ void GetCameraPos(int *X, int *Y) {
+ *X = CameraX;
+ *Y = CameraY;
+ }
+
private:
int Valid;
int Index;
@@ -204,6 +209,11 @@ class Player : public Object {
bool NoShieldsThisLevel = false;
+ int CameraX;
+ int CameraY;
+
+ void UpdateCamera();
+
/* Create a new shot */
int MakeShot(int offset);
/* Rubout a flying shot */