Saltar al contenido
GameGenresEnglish
Acción

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 completa

Una 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

  1. 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 night
  2. El 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"]);
    }
  3. 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
    });
  4. 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);
      });
    }
  5. 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)));
    });
  6. 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)));
  7. 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) });
    } }]);
  8. 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");
// #endregion

Retos 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

  1. 2003
    OrigenStranded

    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)
  2. 2003
    Stranded

    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)
  3. 2009
    Minecraft

    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)
  4. 2013
    Don'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)
  5. 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)
  6. 2016
    P.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)
  7. 2018
    Subnautica

    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)
  8. 2021
    Valheim

    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

Unreal Software (Peter Schauß). Windows

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

Mojang Studios (Markus Persson). Windows, macOS, Linux, Android, iOS

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

Klei Entertainment. Windows, macOS, Linux

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.

2018

Subnautica

Unknown Worlds Entertainment. Windows, macOS, PlayStation 4, Xbox One

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

Iron Gate Studio. Windows, Linux

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.

Fuentes