Created
January 16, 2025 20:55
-
-
Save KrabCode/7a8d2949553e04b6a8f809a5d88f6402 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Should work on APDE | |
// Based on a GDC talk called "Exploring the Tech and Design of Noita" | |
// https://www.youtube.com/watch?v=prXuyMCgbTc | |
private PGraphics pg; | |
float scale = 0.075f; | |
float prevScale = scale; | |
float rectWidth, rectHeight; | |
float fallDirection = 0.05; | |
int w, h; | |
int gx, gy, gw, gh; | |
Particle[][] grid; | |
private float inputSize = 4; | |
int sandColor, waterColor, airColor, rockColor, rockStroke; | |
String[] particleTypes = new String[]{"air", "sand", "water", "rock"}; | |
String selectedType = "water"; | |
boolean prevMousePressed = false; | |
boolean mouseJustPressed = false; | |
public void setup() { | |
fullScreen(); | |
populateGrid(); | |
pg = createGraphics(width, height); | |
} | |
private void populateGrid() { | |
h = ceil(height * scale); | |
w = ceil(width * scale); | |
grid = new Particle[w][h]; | |
for (int x = 0; x < w; x++) { | |
for (int y = 0; y < h; y++) { | |
grid[x][y] = new Particle(); | |
} | |
} | |
} | |
public void draw() { | |
colorMode(HSB, 1, 1, 1, 1); | |
airColor = color(0); | |
sandColor = color(0.8); | |
waterColor = color(0.55, 1, 1); | |
rockColor = color(0.3); | |
rockStroke = color(0.5f); | |
mouseJustPressed = mousePressed && !prevMousePressed; | |
prevMousePressed = mousePressed; | |
if (mousePressedOutsideGui()) { | |
inputParticles(); | |
} | |
spawnParticles(); | |
updateGrid(); | |
pg.beginDraw(); | |
pg.colorMode(HSB, 1, 1, 1, 1); | |
pg.background(0); | |
displayGrid(); | |
displayGUI(); | |
pg.endDraw(); | |
image(pg, 0, 0); | |
} | |
void displayGUI() { | |
gx = width /2; | |
gy = height - 200; | |
gw = 500; | |
gh = 100; | |
pg.rectMode(CENTER); | |
pg.strokeWeight(5); | |
pg.fill(1); | |
for (int i = 0; i < 4; i++) { | |
float iNorm = norm(i, 0, 3); | |
String type = particleTypes[i]; | |
if (type.equals(selectedType)) { | |
pg.stroke(1); | |
} else { | |
pg.stroke(0.3); | |
} | |
if (type.equals("air")) { | |
pg.fill(airColor); | |
} | |
if (type.equals("water")) { | |
pg.fill(waterColor); | |
} | |
if (type.equals("rock")) { | |
pg.fill(rockColor); | |
} | |
if (type.equals("sand")) { | |
pg.fill(sandColor); | |
} | |
float x = gx - gw/2 + iNorm * gw; | |
if (mouseJustPressed && | |
pointRect(mouseX, mouseY, | |
x-gh/2, gy-gh/2, gh, gh)) { | |
selectedType = type; | |
} | |
pg.rect(x, gy, gh, gh); | |
} | |
} | |
boolean pointRect(float px, float py, float rx, float ry, float rw, float rh) { | |
return px >= rx && px <= rx + rw && py >= ry && py <= ry + rh; | |
} | |
boolean mousePressedOutsideGui() { | |
return mousePressed && | |
!pointRect(mouseX, mouseY, | |
gx-gw*0.75, gy-gh*0.75, gw*1.5, gh*1.5); | |
} | |
float particlesSpawned = 0; | |
private void spawnParticles() { | |
String rainType = "water"; | |
float particlesToSpawn = frameCount; | |
while (particlesSpawned < particlesToSpawn) { | |
int x = floor(random(w)); | |
grid[x][0] = new Particle(rainType); | |
particlesSpawned += 2; | |
} | |
} | |
private void updateGrid() { | |
for (int y = 0; y < h; y++) { | |
for (int x = 0; x < w; x++) { | |
Particle p = grid[x][y]; | |
p.hasBeenUpdated = false; | |
} | |
} | |
for (int y = 0; y < h; y++) { | |
for (int x = 0; x < w; x++) { | |
Particle p = grid[x][y]; | |
p.update(x, y); | |
} | |
} | |
} | |
private void displayGrid() { | |
pg.rectMode(CORNER); | |
pg.noStroke(); | |
rectWidth = width / (float) w + 1; | |
rectHeight = height / (float) h + 1; | |
for (int x = 0; x < w; x++) { | |
for (int y = 0; y < h; y++) { | |
float screenX = map(x, 0, w, 0, width); | |
float screenY = map(y, 0, h, 0, height); | |
Particle p = grid[x][y]; | |
pg.fill(p.getFill()); | |
pg.rect(screenX, screenY, rectWidth, rectHeight); | |
} | |
} | |
} | |
void inputParticles() { | |
int inputX = floor(map(mouseX, 0, width, 0, w)); | |
int inputY = floor(map(mouseY, 0, height, 0, h)); | |
for (int x = 0; x < w; x++) { | |
for (int y = 0; y < h; y++) { | |
if (dist(x, y, inputX, inputY) < inputSize) { | |
Particle p = new Particle(selectedType); | |
grid[x][y] = p; | |
} | |
} | |
} | |
} | |
class Particle { | |
String type = "air"; | |
boolean hasBeenUpdated = false; | |
Particle() { | |
} | |
Particle(String type) { | |
this.type = type; | |
} | |
void update(int x, int y) { | |
if (type.equals("air")) { | |
return; | |
} | |
if (type.equals("rock")) { | |
return; | |
} | |
if (type.equals("sand")) { | |
updateSand(x, y); | |
} | |
if (type.equals("water")) { | |
updateWater(x, y); | |
} | |
} | |
int getFill() { | |
if (type.equals("air")) { | |
return airColor; | |
} | |
if (type.equals("sand")) { | |
return sandColor; | |
} | |
if (type.equals("water")) { | |
return waterColor; | |
} | |
if (type.equals("rock")) { | |
return rockColor; | |
} | |
return 0; | |
} | |
boolean isAir(Particle other) { | |
return other.getDensity() == 0; | |
} | |
boolean isLessDenseThanMe(Particle other) { | |
return other.getDensity() < getDensity(); | |
} | |
float getDensity() { | |
if (type.equals("air")) { | |
return 0; | |
} | |
if (type.equals("rock")) { | |
return 100; | |
} | |
if (type.equals("sand")) { | |
return 10; | |
} | |
if (type.equals("water")) { | |
return 2; | |
} | |
return 0; | |
} | |
Particle getParticleSafely(int x, int y) { | |
// avoid index out of bounds, return null instead | |
if (x < 0 || x >= w || y < 0 || y >= h) { | |
return null; | |
} | |
return grid[x][y]; | |
} | |
void swapParticles(int x0, int y0, int x1, int y1) { | |
Particle a = grid[x0][y0]; | |
Particle b = grid[x1][y1]; | |
a.hasBeenUpdated = true; | |
b.hasBeenUpdated = true; | |
grid[x0][y0] = b; | |
grid[x1][y1] = a; | |
} | |
private void updateSand(int x, int y) { | |
Particle bot = getParticleSafely(x, y + 1); | |
Particle botRight = getParticleSafely(x + 1, y + 1); | |
Particle botLeft = getParticleSafely(x - 1, y + 1); | |
if (hasBeenUpdated) { | |
return; | |
} | |
if (bot != null && isLessDenseThanMe(bot)) { | |
swapParticles(x, y, x, y + 1); | |
} else if (botRight != null && isLessDenseThanMe(botRight) && random(1) > fallDirection) { | |
swapParticles(x, y, x + 1, y + 1); | |
} else if (botLeft != null && isLessDenseThanMe(botLeft) && random(1) > fallDirection) { | |
swapParticles(x, y, x - 1, y + 1); | |
} | |
} | |
private void updateWater(int x, int y) { | |
Particle bot = getParticleSafely(x, y + 1); | |
Particle botRight = getParticleSafely(x + 1, y + 1); | |
Particle botLeft = getParticleSafely(x - 1, y + 1); | |
Particle left = getParticleSafely(x - 1, y); | |
Particle right = getParticleSafely(x + 1, y); | |
if (hasBeenUpdated) { | |
return; | |
} | |
if (bot != null && isLessDenseThanMe(bot)) { | |
swapParticles(x, y, x, y + 1); | |
} else if (botRight != null && isLessDenseThanMe(botRight) && random(1) > 0.5) { | |
swapParticles(x, y, x + 1, y + 1); | |
} else if (botLeft != null && isLessDenseThanMe(botLeft)) { | |
swapParticles(x, y, x - 1, y + 1); | |
} else if (left != null && isAir(left) && random(1) > 0.5) { | |
swapParticles(x, y, x - 1, y); | |
} else if (right != null && isAir(right)) { | |
swapParticles(x, y, x + 1, y); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment