
Supervivencia en mundo abierto
Empiezas sin nada en un mundo abierto y tienes que recolectar recursos, fabricar herramientas y construir un refugio para seguir con vida frente al hambre, el clima y las criaturas.
También se llama: supervivencia y fabricación, survival craft, sandbox de supervivencia
Mecánicas que lo definen
- Recolectar recursos del entorno (madera, piedra, comida), a mano al principio y luego con herramientas.
- Recetas de fabricación que convierten materiales en herramientas, armas y construcciones; cada herramienta nueva acelera la recolección o abre recursos que antes no podías tomar.
- Medidores de supervivencia (hambre, sed, temperatura, salud) que bajan con el tiempo y hay que reponer.
- Un ciclo de día y noche en el que el día sirve para recolectar y la noche es más peligrosa.
- Construir un refugio o una base que protege y guarda lo conseguido.
Bucle de juego
Salir a recolectar, volver con materiales, fabricar algo mejor y usarlo para llegar más lejos o conseguir recursos nuevos, sin perder de vista tus medidores.
Demo propia
Controles. Flechas o WASD para moverte; mantén espacio o E junto a un recurso para recolectarlo. 1 fabrica el hacha y 2 el refugio, o haz clic en los botones de recetas. En móvil, los botones en pantalla y tocar las recetas.
Abrir en pantalla completaUna isla vista desde arriba con árboles, rocas y arbustos con bayas. Recolectas madera y piedra, fabricas un hacha para talar más rápido y, con suficiente material, construyes un refugio. Mientras tanto la comida baja, más rápido de noche.
Hecha con Kaplay, en unas 161 líneas de JavaScript.
Cómo está hecha
Configuración y controles
Se crea el juego y se definen botones virtuales para moverse, recolectar y fabricar. Las constantes marcan el ritmo de la partida, como la velocidad a la que baja la comida, cuánto rinde una baya y cuánto duran el día y la noche.
const k = kaplay({ width: 640, height: 360, letterbox: true, pixelDensity, background: [96, 150, 84], buttons: { left: { keyboard: ["left", "a"] }, right: { keyboard: ["right", "d"] }, up: { keyboard: ["up", "w"] }, down: { keyboard: ["down", "s"] }, use: { keyboard: ["space", "e"] }, axe: { keyboard: ["1"] }, shelter: { keyboard: ["2"] }, }, }); touchControls(k, [{ button: "left", label: "◀" }, { button: "up", label: "▲" }, { button: "down", label: "▼" }, { button: "right", label: "▶" }, { button: "use", label: "✋", side: "right" }]); const SPEED = 120; const REACH = 34; // how close you must be to gather something const HUNGER_DRAIN = 3; // food points lost per second during the day const NIGHT_COST = 1.8; // nights are cold: you burn food this many times faster const BERRY_FOOD = 25; const DAY_LENGTH = 25; // seconds of daylight… const NIGHT_LENGTH = 15; // …and of nightEl mundo y sus recursos
Cada tipo de recurso (árbol, roca, arbusto) se describe en una tabla con lo que da, cuántas unidades tiene y cuánto tarda en sacarse una a mano. Agregar un recurso nuevo es añadir una fila. Las posiciones son fijas para que todas las partidas empiecen igual.
// Fixed positions, so every game starts the same and is easy to compare in class. const TREES = [[90, 90], [150, 250], [230, 120], [300, 270], [380, 85], [455, 205], [525, 105], [585, 270], [120, 175], [545, 185]]; const ROCKS = [[205, 205], [340, 160], [480, 285], [600, 80], [60, 280]]; const BUSHES = [[265, 70], [420, 290], [610, 175]]; // Each kind of resource: what it gives, how much it holds and how long one unit takes by hand. const KINDS = { tree: { item: "wood", amount: 3, time: 1.6, shape: () => circle(15), color: [34, 100, 48] }, rock: { item: "stone", amount: 3, time: 1.4, shape: () => polygon([vec2(-14, 6), vec2(-8, -10), vec2(8, -12), vec2(15, 4), vec2(4, 12)]), color: [150, 150, 160] }, bush: { item: "food", amount: 2, time: 0.4, shape: () => circle(11), color: [50, 120, 60] }, }; function spawn(kind, [x, y]) { const def = KINDS[kind]; return add([def.shape(), color(...def.color), pos(x, y), anchor("center"), area(), scale(1), z(2), { kind, amount: def.amount, progress: 0, regrow: 0 }, "resource"]); }El jugador y su inventario
El inventario se guarda en el propio objeto del jugador, con la comida, la madera, la piedra y si tiene hacha. El jugador se mueve en ocho direcciones y no puede salir de la isla.
// The inventory lives on the player: food (the hunger meter), wood, stone and tools. const player = add([circle(9), color(250, 214, 140), outline(2, rgb(90, 60, 30)), pos(320, 190), anchor("center"), z(5), { food: 100, wood: 0, stone: 0, axe: false }, "player"]); player.onUpdate(() => { const axis = (a, b) => (isButtonDown(b) ? 1 : 0) - (isButtonDown(a) ? 1 : 0); const dir = vec2(axis("left", "right"), axis("up", "down")); if (dir.len() > 0) player.move(dir.unit().scale(SPEED)); player.pos = vec2(clamp(player.pos.x, 10, 630), clamp(player.pos.y, 50, 306)); // stay on the island });Recolectar
Si mantienes el botón junto a un recurso, una barra se llena y al completarse ganas una unidad. Si te alejas, pierdes lo avanzado. Cada vez que sacas algo, el recurso se encoge; los árboles y las rocas desaparecen al agotarse, pero los arbustos vuelven a dar bayas. El tiempo de la barra depende de tus herramientas, y eso es lo que mejora fabricando.
// Hold "use" next to a resource: a bar fills up and you get one unit. // How long it takes depends on your tools, and that is what crafting improves. const gatherTime = (r) => (r.kind === "tree" && player.axe ? 0.45 : KINDS[r.kind].time); let focus = null; // the resource you are next to, if any player.onUpdate(() => { const near = get("resource").filter((r) => r.amount > 0 && r.pos.dist(player.pos) < REACH); near.sort((a, b) => a.pos.dist(player.pos) - b.pos.dist(player.pos)); if (focus && focus !== near[0]) focus.progress = 0; // walking away loses the progress focus = near[0] ?? null; if (!focus || !isButtonDown("use")) { if (focus) focus.progress = 0; return; } focus.progress += dt() / gatherTime(focus); if (focus.progress < 1) return; focus.progress = 0; focus.amount--; const item = KINDS[focus.kind].item; if (item === "food") player.food = Math.min(100, player.food + BERRY_FOOD); else player[item]++; popup(focus.pos, item); if (focus.amount === 0 && focus.kind !== "bush") destroy(focus); // bushes grow back, trees do not else focus.scale = vec2(0.6 + (0.4 * focus.amount) / KINDS[focus.kind].amount); }); const NAMES = { wood: { es: "madera", en: "wood" }, stone: { es: "piedra", en: "stone" }, food: { es: "comida", en: "food" } }; function popup(p, item) { const label = add([text("+1 " + t(NAMES[item]), { size: 13, font: FONT }), pos(p.sub(0, 20)), anchor("center"), opacity(1), z(9)]); label.onUpdate(() => { label.pos.y -= 30 * dt(); label.opacity -= dt(); if (label.opacity <= 0) destroy(label); }); }Fabricar
Cada receta dice cuánta madera y piedra necesita. Fabricar resta los materiales y cambia algo del juego. El hacha hace que talar sea más de tres veces más rápido, y el refugio te da la victoria. Los botones se iluminan cuando tienes material suficiente, así el jugador ve su próxima meta.
// A recipe turns materials into something that changes how you play. const RECIPES = { axe: { wood: 3, stone: 2, name: { es: "Hacha", en: "Axe" } }, shelter: { wood: 15, stone: 9, name: { es: "Refugio", en: "Shelter" } }, }; const canCraft = (id) => player.wood >= RECIPES[id].wood && player.stone >= RECIPES[id].stone && !(id === "axe" && player.axe); function craft(id) { if (id === "axe" && player.axe) return say(t({ es: "Ya tienes un hacha.", en: "You already have an axe." })); if (!canCraft(id)) return say(t({ es: "Te faltan materiales.", en: "Not enough materials." })); player.wood -= RECIPES[id].wood; player.stone -= RECIPES[id].stone; if (id === "shelter") return go("end", true, day()); player.axe = true; say(t({ es: "¡Hacha! Ahora talas mucho más rápido.", en: "Axe! Now you chop much faster." })); } onButtonPress("axe", () => craft("axe")); onButtonPress("shelter", () => craft("shelter")); // On-screen recipe buttons: click or tap them (or press 1 and 2). Object.keys(RECIPES).forEach((id, i) => { const r = RECIPES[id]; const button = add([rect(250, 30, { radius: 6 }), pos(70 + i * 270, 322), area(), color(60, 70, 60), z(11)]); button.add([text(`(${i + 1}) ${t(r.name)}: ${r.wood} ${t(NAMES.wood)} + ${r.stone} ${t(NAMES.stone)}`, { size: 13, font: FONT }), pos(125, 15), anchor("center")]); button.onClick(() => craft(id)); button.onUpdate(() => (button.color = canCraft(id) ? rgb(70, 140, 70) : rgb(60, 70, 60))); });Hambre, día y noche
La comida baja todo el tiempo, y si llega a cero pierdes. De noche baja casi el doble, por el frío. Un reloj cuenta los segundos y de ahí salen el número de día y si es de noche. La noche es una capa oscura cuya opacidad se acerca poco a poco a la que toca, para que el cambio sea gradual. Esta presión obliga a decidir entre comer o seguir recolectando.
// Food drops all the time, faster at night; at zero you lose. // Bushes grow a new berry every few seconds, so food is renewable but slow. let clock = 0; const day = () => Math.floor(clock / (DAY_LENGTH + NIGHT_LENGTH)) + 1; const isNight = () => clock % (DAY_LENGTH + NIGHT_LENGTH) > DAY_LENGTH; onUpdate(() => { clock += dt(); messageTime -= dt(); player.food -= HUNGER_DRAIN * (isNight() ? NIGHT_COST : 1) * dt(); if (player.food <= 0) go("end", false, day()); for (const b of get("resource").filter((r) => r.kind === "bush" && r.amount < 3)) if ((b.regrow += dt()) > 8) (b.regrow = 0), b.amount++, (b.scale = vec2(0.6 + (0.4 * b.amount) / 3)); }); // Night is a dark layer whose opacity follows the clock. const night = add([rect(width(), height()), color(10, 14, 40), opacity(0), z(8)]); night.onUpdate(() => (night.opacity = lerp(night.opacity, isNight() ? 0.55 : 0, dt() * 2)));El marcador
La barra de comida cambia a rojo cuando queda poca. El texto muestra el inventario y el día. Las bayas se dibujan sobre los arbustos y la barra de recolección sobre el recurso con el que trabajas, porque la información importante debe estar donde miras.
add([z(10), { draw() { // Berries on the bushes and the gathering bar over the resource you are working on. for (const b of get("resource").filter((r) => r.kind === "bush")) for (let i = 0; i < b.amount; i++) drawCircle({ pos: b.pos.add(-6 + i * 6, -2 + (i % 2) * 5), radius: 3, color: rgb(220, 40, 60) }); if (focus) { drawRect({ pos: focus.pos.add(-16, -26), width: 32, height: 5, color: rgb(30, 30, 30) }); drawRect({ pos: focus.pos.add(-16, -26), width: 32 * focus.progress, height: 5, color: rgb(255, 230, 120) }); } drawRect({ width: width(), height: 40, color: rgb(30, 36, 30), opacity: 0.85 }); drawText({ text: t({ es: "Comida", en: "Food" }), pos: vec2(12, 12), size: 14, font: FONT }); drawRect({ pos: vec2(70, 12), width: 120, height: 14, color: rgb(70, 40, 40) }); drawRect({ pos: vec2(70, 12), width: 1.2 * Math.max(0, player.food), height: 14, color: player.food < 30 ? rgb(230, 70, 60) : rgb(240, 170, 60) }); const axe = player.axe ? t({ es: "sí", en: "yes" }) : t({ es: "no", en: "no" }); const when = isNight() ? t({ es: " · noche", en: " · night" }) : ""; drawText({ text: `${t({ es: "Madera", en: "Wood" })} ${player.wood} ${t({ es: "Piedra", en: "Stone" })} ${player.stone} ${t({ es: "Hacha", en: "Axe" })}: ${axe} ${t({ es: "Día", en: "Day" })} ${day()}${when}`, pos: vec2(205, 12), size: 14, font: FONT }); if (messageTime > 0) drawText({ text: message, pos: vec2(width() / 2, 298), size: 14, font: FONT, anchor: "center", color: rgb(255, 245, 200) }); } }]);Escenas
El juego tiene tres escenas (title, game y end), y go() salta de una a otra. Al final se muestra en qué día terminaste, para que compares partidas.
function screen(title, subtitle, next) { add([text(title, { size: 34, font: FONT, align: "center" }), pos(center().sub(0, 50)), anchor("center")]); add([text(subtitle, { size: 15, font: FONT, align: "center", width: 560 }), pos(center().add(0, 30)), anchor("center"), color(235, 245, 225)]); onKeyPress(["enter"], next); onButtonPress("use", next); onClick(next); } scene("title", () => screen( t({ es: "Supervivencia en mundo abierto", en: "Open-world survival craft" }), t({ es: "Flechas o WASD para moverte. Mantén espacio (o E) junto a un árbol, una roca o un arbusto para recolectar.\nFabrica un hacha (1) para talar más rápido y un refugio (2) para ganar.\nNo dejes que la comida llegue a cero: de noche hace frío y gastas más.\nPulsa espacio o toca para empezar.", en: "Arrows or WASD to move. Hold space (or E) next to a tree, a rock or a bush to gather.\nCraft an axe (1) to chop faster and a shelter (2) to win.\nDo not let your food reach zero: nights are cold and you burn more.\nPress space or tap to start.", }), () => go("game"), ), ); scene("end", (won, days) => screen( won ? t({ es: "Refugio construido", en: "Shelter built" }) : t({ es: "Te venció el hambre", en: "Hunger got you" }), t({ es: `Día ${days}. Pulsa espacio o toca para jugar de nuevo.`, en: `Day ${days}. Press space or tap to play again.` }), () => go("game"), ), ); go("title");
Qué simplificamos
- La isla cabe en una pantalla y siempre es igual; en el género real el mundo es grande y suele generarse al azar.
- Hay un solo medidor (comida) y dos recetas; no hay sed, temperatura ni criaturas que te ataquen.
- La noche no trae peligros nuevos, solo oscurece y hace que gastes comida más rápido por el frío.
Para modificarla
- Agrega una fogata (madera + piedra) que, si estás cerca, frene el gasto de comida durante la noche.
- Haz que los árboles talados dejen un brote que vuelva a crecer, y piensa cómo cambia la estrategia.
- Añade un pico que sea necesario para sacar piedra de las rocas grandes.
Código completo
// Survival craft demo
// Engine: Kaplay 3001 (https://kaplayjs.com). MIT licence.
import kaplay from "../../vendor/kaplay.mjs";
import { t, FONT, touchControls, pixelDensity } from "../common.js";
// #region setup
const k = kaplay({
width: 640,
height: 360,
letterbox: true, pixelDensity,
background: [96, 150, 84],
buttons: {
left: { keyboard: ["left", "a"] }, right: { keyboard: ["right", "d"] },
up: { keyboard: ["up", "w"] }, down: { keyboard: ["down", "s"] },
use: { keyboard: ["space", "e"] },
axe: { keyboard: ["1"] },
shelter: { keyboard: ["2"] },
},
});
touchControls(k, [{ button: "left", label: "◀" }, { button: "up", label: "▲" }, { button: "down", label: "▼" },
{ button: "right", label: "▶" }, { button: "use", label: "✋", side: "right" }]);
const SPEED = 120;
const REACH = 34; // how close you must be to gather something
const HUNGER_DRAIN = 3; // food points lost per second during the day
const NIGHT_COST = 1.8; // nights are cold: you burn food this many times faster
const BERRY_FOOD = 25;
const DAY_LENGTH = 25; // seconds of daylight…
const NIGHT_LENGTH = 15; // …and of night
// #endregion
// #region world
// Fixed positions, so every game starts the same and is easy to compare in class.
const TREES = [[90, 90], [150, 250], [230, 120], [300, 270], [380, 85], [455, 205], [525, 105], [585, 270], [120, 175], [545, 185]];
const ROCKS = [[205, 205], [340, 160], [480, 285], [600, 80], [60, 280]];
const BUSHES = [[265, 70], [420, 290], [610, 175]];
// Each kind of resource: what it gives, how much it holds and how long one unit takes by hand.
const KINDS = {
tree: { item: "wood", amount: 3, time: 1.6, shape: () => circle(15), color: [34, 100, 48] },
rock: { item: "stone", amount: 3, time: 1.4, shape: () => polygon([vec2(-14, 6), vec2(-8, -10), vec2(8, -12), vec2(15, 4), vec2(4, 12)]), color: [150, 150, 160] },
bush: { item: "food", amount: 2, time: 0.4, shape: () => circle(11), color: [50, 120, 60] },
};
function spawn(kind, [x, y]) {
const def = KINDS[kind];
return add([def.shape(), color(...def.color), pos(x, y), anchor("center"), area(), scale(1), z(2),
{ kind, amount: def.amount, progress: 0, regrow: 0 }, "resource"]);
}
// #endregion
scene("game", () => {
for (const p of TREES) spawn("tree", p);
for (const p of ROCKS) spawn("rock", p);
for (const p of BUSHES) spawn("bush", p);
let message = "";
let messageTime = 0;
const say = (m) => ((message = m), (messageTime = 2.5));
// #region player
// The inventory lives on the player: food (the hunger meter), wood, stone and tools.
const player = add([circle(9), color(250, 214, 140), outline(2, rgb(90, 60, 30)), pos(320, 190), anchor("center"),
z(5), { food: 100, wood: 0, stone: 0, axe: false }, "player"]);
player.onUpdate(() => {
const axis = (a, b) => (isButtonDown(b) ? 1 : 0) - (isButtonDown(a) ? 1 : 0);
const dir = vec2(axis("left", "right"), axis("up", "down"));
if (dir.len() > 0) player.move(dir.unit().scale(SPEED));
player.pos = vec2(clamp(player.pos.x, 10, 630), clamp(player.pos.y, 50, 306)); // stay on the island
});
// #endregion
// #region gathering
// Hold "use" next to a resource: a bar fills up and you get one unit.
// How long it takes depends on your tools, and that is what crafting improves.
const gatherTime = (r) => (r.kind === "tree" && player.axe ? 0.45 : KINDS[r.kind].time);
let focus = null; // the resource you are next to, if any
player.onUpdate(() => {
const near = get("resource").filter((r) => r.amount > 0 && r.pos.dist(player.pos) < REACH);
near.sort((a, b) => a.pos.dist(player.pos) - b.pos.dist(player.pos));
if (focus && focus !== near[0]) focus.progress = 0; // walking away loses the progress
focus = near[0] ?? null;
if (!focus || !isButtonDown("use")) {
if (focus) focus.progress = 0;
return;
}
focus.progress += dt() / gatherTime(focus);
if (focus.progress < 1) return;
focus.progress = 0;
focus.amount--;
const item = KINDS[focus.kind].item;
if (item === "food") player.food = Math.min(100, player.food + BERRY_FOOD);
else player[item]++;
popup(focus.pos, item);
if (focus.amount === 0 && focus.kind !== "bush") destroy(focus); // bushes grow back, trees do not
else focus.scale = vec2(0.6 + (0.4 * focus.amount) / KINDS[focus.kind].amount);
});
const NAMES = { wood: { es: "madera", en: "wood" }, stone: { es: "piedra", en: "stone" }, food: { es: "comida", en: "food" } };
function popup(p, item) {
const label = add([text("+1 " + t(NAMES[item]), { size: 13, font: FONT }), pos(p.sub(0, 20)), anchor("center"), opacity(1), z(9)]);
label.onUpdate(() => {
label.pos.y -= 30 * dt();
label.opacity -= dt();
if (label.opacity <= 0) destroy(label);
});
}
// #endregion
// #region crafting
// A recipe turns materials into something that changes how you play.
const RECIPES = {
axe: { wood: 3, stone: 2, name: { es: "Hacha", en: "Axe" } },
shelter: { wood: 15, stone: 9, name: { es: "Refugio", en: "Shelter" } },
};
const canCraft = (id) => player.wood >= RECIPES[id].wood && player.stone >= RECIPES[id].stone && !(id === "axe" && player.axe);
function craft(id) {
if (id === "axe" && player.axe) return say(t({ es: "Ya tienes un hacha.", en: "You already have an axe." }));
if (!canCraft(id)) return say(t({ es: "Te faltan materiales.", en: "Not enough materials." }));
player.wood -= RECIPES[id].wood;
player.stone -= RECIPES[id].stone;
if (id === "shelter") return go("end", true, day());
player.axe = true;
say(t({ es: "¡Hacha! Ahora talas mucho más rápido.", en: "Axe! Now you chop much faster." }));
}
onButtonPress("axe", () => craft("axe"));
onButtonPress("shelter", () => craft("shelter"));
// On-screen recipe buttons: click or tap them (or press 1 and 2).
Object.keys(RECIPES).forEach((id, i) => {
const r = RECIPES[id];
const button = add([rect(250, 30, { radius: 6 }), pos(70 + i * 270, 322), area(), color(60, 70, 60), z(11)]);
button.add([text(`(${i + 1}) ${t(r.name)}: ${r.wood} ${t(NAMES.wood)} + ${r.stone} ${t(NAMES.stone)}`, { size: 13, font: FONT }), pos(125, 15), anchor("center")]);
button.onClick(() => craft(id));
button.onUpdate(() => (button.color = canCraft(id) ? rgb(70, 140, 70) : rgb(60, 70, 60)));
});
// #endregion
// #region survival
// Food drops all the time, faster at night; at zero you lose.
// Bushes grow a new berry every few seconds, so food is renewable but slow.
let clock = 0;
const day = () => Math.floor(clock / (DAY_LENGTH + NIGHT_LENGTH)) + 1;
const isNight = () => clock % (DAY_LENGTH + NIGHT_LENGTH) > DAY_LENGTH;
onUpdate(() => {
clock += dt();
messageTime -= dt();
player.food -= HUNGER_DRAIN * (isNight() ? NIGHT_COST : 1) * dt();
if (player.food <= 0) go("end", false, day());
for (const b of get("resource").filter((r) => r.kind === "bush" && r.amount < 3))
if ((b.regrow += dt()) > 8) (b.regrow = 0), b.amount++, (b.scale = vec2(0.6 + (0.4 * b.amount) / 3));
});
// Night is a dark layer whose opacity follows the clock.
const night = add([rect(width(), height()), color(10, 14, 40), opacity(0), z(8)]);
night.onUpdate(() => (night.opacity = lerp(night.opacity, isNight() ? 0.55 : 0, dt() * 2)));
// #endregion
// #region hud
add([z(10), { draw() {
// Berries on the bushes and the gathering bar over the resource you are working on.
for (const b of get("resource").filter((r) => r.kind === "bush"))
for (let i = 0; i < b.amount; i++) drawCircle({ pos: b.pos.add(-6 + i * 6, -2 + (i % 2) * 5), radius: 3, color: rgb(220, 40, 60) });
if (focus) {
drawRect({ pos: focus.pos.add(-16, -26), width: 32, height: 5, color: rgb(30, 30, 30) });
drawRect({ pos: focus.pos.add(-16, -26), width: 32 * focus.progress, height: 5, color: rgb(255, 230, 120) });
}
drawRect({ width: width(), height: 40, color: rgb(30, 36, 30), opacity: 0.85 });
drawText({ text: t({ es: "Comida", en: "Food" }), pos: vec2(12, 12), size: 14, font: FONT });
drawRect({ pos: vec2(70, 12), width: 120, height: 14, color: rgb(70, 40, 40) });
drawRect({ pos: vec2(70, 12), width: 1.2 * Math.max(0, player.food), height: 14, color: player.food < 30 ? rgb(230, 70, 60) : rgb(240, 170, 60) });
const axe = player.axe ? t({ es: "sí", en: "yes" }) : t({ es: "no", en: "no" });
const when = isNight() ? t({ es: " · noche", en: " · night" }) : "";
drawText({ text: `${t({ es: "Madera", en: "Wood" })} ${player.wood} ${t({ es: "Piedra", en: "Stone" })} ${player.stone} ${t({ es: "Hacha", en: "Axe" })}: ${axe} ${t({ es: "Día", en: "Day" })} ${day()}${when}`, pos: vec2(205, 12), size: 14, font: FONT });
if (messageTime > 0) drawText({ text: message, pos: vec2(width() / 2, 298), size: 14, font: FONT, anchor: "center", color: rgb(255, 245, 200) });
} }]);
// #endregion
});
// #region scenes
function screen(title, subtitle, next) {
add([text(title, { size: 34, font: FONT, align: "center" }), pos(center().sub(0, 50)), anchor("center")]);
add([text(subtitle, { size: 15, font: FONT, align: "center", width: 560 }), pos(center().add(0, 30)), anchor("center"), color(235, 245, 225)]);
onKeyPress(["enter"], next);
onButtonPress("use", next);
onClick(next);
}
scene("title", () =>
screen(
t({ es: "Supervivencia en mundo abierto", en: "Open-world survival craft" }),
t({
es: "Flechas o WASD para moverte. Mantén espacio (o E) junto a un árbol, una roca o un arbusto para recolectar.\nFabrica un hacha (1) para talar más rápido y un refugio (2) para ganar.\nNo dejes que la comida llegue a cero: de noche hace frío y gastas más.\nPulsa espacio o toca para empezar.",
en: "Arrows or WASD to move. Hold space (or E) next to a tree, a rock or a bush to gather.\nCraft an axe (1) to chop faster and a shelter (2) to win.\nDo not let your food reach zero: nights are cold and you burn more.\nPress space or tap to start.",
}),
() => go("game"),
),
);
scene("end", (won, days) =>
screen(
won ? t({ es: "Refugio construido", en: "Shelter built" }) : t({ es: "Te venció el hambre", en: "Hunger got you" }),
t({ es: `Día ${days}. Pulsa espacio o toca para jugar de nuevo.`, en: `Day ${days}. Press space or tap to play again.` }),
() => go("game"),
),
);
go("title");
// #endregionRetos de diseño
- Equilibrar lo rápido que bajan los medidores con lo rápido que se recolecta, para que haya presión sin volverse tedioso.
- Un árbol de recetas que marque metas claras y en el que cada fabricación cambie de verdad cómo juegas.
- Que recolectar, lo que más se repite, se sienta bien gracias a la respuesta visual y sonora.
- Dar dirección en un mundo abierto sin quitarle libertad al jugador.
Historia
- 2003OrigenStranded
Stranded (2003), un juego gratuito y sencillo sobre un náufrago en una isla, ya tenía un planteamiento muy parecido al de los juegos de supervivencia actuales; Minecraft (2009), con su ciclo de recolectar y fabricar de día y defenderte de noche, popularizó la fórmula.
Survival game (Wikipedia) - 2003Stranded
En Stranded, de Unreal Software, un náufrago controla el hambre, la sed y el sueño, recoge ramas, piedras y lianas, fabrica herramientas y refugios, y construye una balsa para escapar.
Stranded (video game) (Wikipedia) - 2009Minecraft
Sale la primera versión pública de Minecraft; en su modo supervivencia recolectas madera y piedra para fabricar, comes para no morir de hambre y necesitas refugio porque de noche aparecen monstruos.
Minecraft (Wikipedia) - 2013Don't Starve
Don't Starve, de Klei Entertainment, suma al hambre y la salud un medidor de cordura, noches en las que la oscuridad mata y muerte permanente en un mundo generado al azar.
Don't Starve (Wikipedia) - 2013
Rust, de Facepunch, entra en acceso anticipado: solo multijugador, empiezas con una piedra y una antorcha, y la amenaza principal son los demás jugadores, que pueden asaltar tu base.
Rust (video game) (Wikipedia) - 2016P.Craft
NuSan publica P.Craft para la consola de fantasía PICO-8: una isla, árboles que talar, un banco de trabajo y una balsa para escapar, todo jugable en el navegador.
P.Craft (Lexaloffle BBS, PICO-8) - 2018Subnautica
Subnautica lleva el género bajo el agua, con oxígeno, hambre y sed que vigilar, y sus autores deciden no incluir armas letales.
Subnautica (Wikipedia) - 2021Valheim
Valheim, del estudio sueco Iron Gate, vende cinco millones de copias en su primer mes de acceso anticipado, con hasta diez jugadores construyendo y sobreviviendo juntos.
Valheim (Wikipedia)
Juegos de referencia
2003
Stranded
Eres un náufrago en una isla desierta. Controlas el hambre, la sed y el sueño, recoges ramas, piedras y lianas, fabricas herramientas y refugios, y construyes una balsa para escapar. Sus autores lo publicaron gratis.
2009
Minecraft
Un mundo de bloques generado al azar. En el modo supervivencia recolectas madera y piedra para fabricar herramientas, comes para no morir de hambre y te refugias de noche, cuando salen los monstruos; en el modo creativo solo construyes.
2013
Don't Starve
Sobrevives en un mundo generado al azar vigilando tres medidores, hambre, salud y cordura, mientras recolectas y fabricas con recetas. De noche la oscuridad mata si no tienes luz, y la muerte es permanente.
2016
P.Craft
Despiertas en una isla desierta, golpeas árboles para conseguir madera, fabricas un banco de trabajo y herramientas cada vez mejores, evitas a los zombis y construyes una barca para escapar. Está inspirado en Minicraft, de Notch.
2018
Subnautica
Tras estrellarte en un planeta oceánico, exploras el fondo del mar vigilando el oxígeno, el hambre y la sed, recoges recursos y planos, construyes bases y vehículos submarinos, y buscas cómo escapar. No tiene armas letales.
2021
Valheim
Náufragos de inspiración nórdica en un mundo generado por regiones. Recolectas materiales, fabricas herramientas y equipo, levantas refugios y derrotas jefes, solo o con hasta diez personas.
Ejercicio
En la demo, cambia el coste del hacha y del refugio y lo rápido que baja la comida, y mide cuánto tardas en ganar con y sin hacha. Luego añade una receta nueva (por ejemplo, una fogata que frene el gasto de comida de noche) y discute en qué orden conviene fabricar las cosas.