<!-- 
   see the video tutorial for how this kind of thing works:
   https://youtu.be/H3Fn33lYuE0
-->

<div class="frame">
   <div class="corner_topleft"></div>
   <div class="corner_topright"></div>
   <div class="corner_bottomleft"></div>
   <div class="corner_bottomright"></div>
   
   
   <div class="camera">
      <div class="map pixel-art">
         <div class="character" facing="down" walking="true">
            <div class="shadow pixel-art"></div>
            <div class="character_spritesheet pixel-art"></div>
         </div>
      </div>


      <div class="dpad">
         <div class="DemoDirectionUI flex-center">
               <button class="dpad-button dpad-left">
                   <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -0.5 13 13" shape-rendering="crispEdges">
                       <path class="Arrow_outline-top"  stroke="#5f5f5f" d="M1 0h11M0 1h1M12 1h1M0 2h1M12 2h1M0 3h1M12 3h1M0 4h1M12 4h1M0 5h1M12 5h1M0 6h1M12 6h1M0 7h1M12 7h1M0 8h1M12 8h1" />
                       <path class="Arrow_surface" stroke="#f5f5f5" d="M1 1h11M1 2h11M1 3h5M7 3h5M1 4h4M7 4h5M1 5h3M7 5h5M1 6h4M7 6h5M1 7h5M7 7h5M1 8h11" />
                       <path class="Arrow_arrow-inset"  stroke="#434343" d="M6 3h1M5 4h1M4 5h1" />
                       <path class="Arrow_arrow-body" stroke="#5f5f5f" d="M6 4h1M5 5h2M5 6h2M6 7h1" />
                       <path class="Arrow_outline-bottom" stroke="#434343" d="M0 9h1M12 9h1M0 10h1M12 10h1M0 11h1M12 11h1M1 12h11" />
                       <path class="Arrow_edge" stroke="#ffffff" d="M1 9h11" />
                       <path class="Arrow_front" stroke="#cccccc" d="M1 10h11M1 11h11" />
                   </svg>
               </button>
               <button class="dpad-button dpad-up">
                   <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -0.5 13 13" shape-rendering="crispEdges">
                       <path class="Arrow_outline-top"  stroke="#5f5f5f" d="M1 0h11M0 1h1M12 1h1M0 2h1M12 2h1M0 3h1M12 3h1M0 4h1M12 4h1M0 5h1M12 5h1M0 6h1M12 6h1M0 7h1M12 7h1M0 8h1M12 8h1" />
                       <path class="Arrow_surface" stroke="#f5f5f5" d="M1 1h11M1 2h11M1 3h11M1 4h5M7 4h5M1 5h4M8 5h4M1 6h3M9 6h3M1 7h11M1 8h11" />
                       <path class="Arrow_arrow-inset"  stroke="#434343" d="M6 4h1M5 5h1M7 5h1" />
                       <path class="Arrow_arrow-body" stroke="#5f5f5f" d="M6 5h1M4 6h5" />
                       <path class="Arrow_outline-bottom" stroke="#434343" d="M0 9h1M12 9h1M0 10h1M12 10h1M0 11h1M12 11h1M1 12h11" />
                       <path class="Arrow_edge" stroke="#ffffff" d="M1 9h11" />
                       <path class="Arrow_front" stroke="#cccccc" d="M1 10h11M1 11h11" />
                   </svg>
               </button>
               <button class="dpad-button dpad-down">
                   <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -0.5 13 13" shape-rendering="crispEdges">
                       <path class="Arrow_outline-top" stroke="#5f5f5f" d="M1 0h11M0 1h1M12 1h1M0 2h1M12 2h1M0 3h1M12 3h1M0 4h1M12 4h1M0 5h1M12 5h1M0 6h1M12 6h1M0 7h1M12 7h1M0 8h1M12 8h1" />
                       <path class="Arrow_surface" stroke="#f5f5f5" d="M1 1h11M1 2h11M1 3h11M1 4h3M9 4h3M1 5h4M8 5h4M1 6h5M7 6h5M1 7h11M1 8h11" />
                       <path class="Arrow_arrow-inset" stroke="#434343" d="M4 4h5" />
                       <path class="Arrow_arrow-body" stroke="#5f5f5f" d="M5 5h3M6 6h1" />
                       <path class="Arrow_outline-bottom" stroke="#434343" d="M0 9h1M12 9h1M0 10h1M12 10h1M0 11h1M12 11h1M1 12h11" />
                       <path class="Arrow_edge" stroke="#ffffff" d="M1 9h11" />
                       <path class="Arrow_front" stroke="#cccccc" d="M1 10h11M1 11h11" />
                   </svg>
               </button>
               <button class="dpad-button dpad-right">
                   <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -0.5 13 13" shape-rendering="crispEdges">
                       <path class="Arrow_outline-top"  stroke="#5f5f5f" d="M1 0h11M0 1h1M12 1h1M0 2h1M12 2h1M0 3h1M12 3h1M0 4h1M12 4h1M0 5h1M12 5h1M0 6h1M12 6h1M0 7h1M12 7h1M0 8h1M12 8h1" />
                       <path class="Arrow_surface" stroke="#f5f5f5" d="M1 1h11M1 2h11M1 3h5M7 3h5M1 4h5M8 4h4M1 5h5M9 5h3M1 6h5M8 6h4M1 7h5M7 7h5M1 8h11" />
                       <path class="Arrow_arrow-inset"  stroke="#434343" d="M6 3h1M7 4h1M8 5h1" />
                       <path class="Arrow_arrow-body" stroke="#5f5f5f" d="M6 4h1M6 5h2M6 6h2M6 7h1" />
                       <path class="Arrow_outline-bottom" stroke="#434343" d="M0 9h1M12 9h1M0 10h1M12 10h1M0 11h1M12 11h1M1 12h11" />
                       <path class="Arrow_edge" stroke="#ffffff" d="M1 9h11" />
                       <path class="Arrow_front" stroke="#cccccc" d="M1 10h11M1 11h11" />
                   </svg>
               </button>
           </div>
      </div>
      
      
      <svg class="headline" xmlns="http://www.w3.org/2000/svg" viewBox="0 -0.5 75 14" shape-rendering="crispEdges">
<metadata>Made with Pixels to Svg https://codepen.io/shshaw/pen/XbxvNj</metadata>
<path stroke="rgba(128,191,255,0.01568627450980392)" d="M0 0h1M74 0h1M0 13h1M74 13h1" />
<path stroke="#5f5f5f" d="M1 0h73M0 1h1M74 1h1M0 2h1M74 2h1M0 3h1M74 3h1M0 4h1M74 4h1M0 5h1M74 5h1M0 6h1M74 6h1M0 7h1M74 7h1M0 8h1M74 8h1M0 9h1M74 9h1M0 10h1M74 10h1M0 11h1M74 11h1" />
<path stroke="#f5f5f5" d="M1 1h73M1 2h73M1 3h3M7 3h12M21 3h22M45 3h29M1 4h4M6 4h13M20 4h1M22 4h20M43 4h2M46 4h28M1 5h4M6 5h2M10 5h2M15 5h4M20 5h2M23 5h2M27 5h2M30 5h1M32 5h1M34 5h1M38 5h4M43 5h5M50 5h2M56 5h3M61 5h3M66 5h1M69 5h5M1 6h4M6 6h1M8 6h2M11 6h1M13 6h2M16 6h3M20 6h2M23 6h1M25 6h2M28 6h1M30 6h1M32 6h1M34 6h1M36 6h2M39 6h3M43 6h7M51 6h1M53 6h1M55 6h1M57 6h1M59 6h2M62 6h1M64 6h5M70 6h4M1 7h4M6 7h1M8 7h2M11 7h1M13 7h2M16 7h3M20 7h2M23 7h1M25 7h2M28 7h1M30 7h1M32 7h1M34 7h1M36 7h2M39 7h3M43 7h5M51 7h1M53 7h1M55 7h1M57 7h1M62 7h1M64 7h3M70 7h4M1 8h4M6 8h1M8 8h2M11 8h1M13 8h2M16 8h3M20 8h1M22 8h2M25 8h2M28 8h1M30 8h1M32 8h1M34 8h1M36 8h2M39 8h3M43 8h2M46 8h1M48 8h2M51 8h1M53 8h1M55 8h1M57 8h1M59 8h4M64 8h2M67 8h2M70 8h4M1 9h4M6 9h2M10 9h2M15 9h4M21 9h4M27 9h3M34 9h1M36 9h2M39 9h4M45 9h3M51 9h1M53 9h1M55 9h1M57 9h2M62 9h1M64 9h3M70 9h4M1 10h11M13 10h61M1 11h11M13 11h61" />
<path stroke="#323234" d="M4 3h3M19 3h2M43 3h2M5 4h1M19 4h1M21 4h1M42 4h1M45 4h1M5 5h1M8 5h2M12 5h3M19 5h1M22 5h1M25 5h2M29 5h1M31 5h1M33 5h1M35 5h3M42 5h1M48 5h2M52 5h4M59 5h2M64 5h2M67 5h2M5 6h1M7 6h1M10 6h1M12 6h1M15 6h1M19 6h1M22 6h1M24 6h1M27 6h1M29 6h1M31 6h1M33 6h1M35 6h1M38 6h1M42 6h1M50 6h1M52 6h1M54 6h1M56 6h1M58 6h1M61 6h1M63 6h1M69 6h1M5 7h1M7 7h1M10 7h1M12 7h1M15 7h1M19 7h1M22 7h1M24 7h1M27 7h1M29 7h1M31 7h1M33 7h1M35 7h1M38 7h1M42 7h1M48 7h3M52 7h1M54 7h1M56 7h1M58 7h4M63 7h1M67 7h3M5 8h1M7 8h1M10 8h1M12 8h1M15 8h1M19 8h1M21 8h1M24 8h1M27 8h1M29 8h1M31 8h1M33 8h1M35 8h1M38 8h1M42 8h1M45 8h1M47 8h1M50 8h1M52 8h1M54 8h1M56 8h1M58 8h1M63 8h1M66 8h1M69 8h1M5 9h1M8 9h2M12 9h3M19 9h2M25 9h2M30 9h4M35 9h1M38 9h1M43 9h2M48 9h3M52 9h1M54 9h1M56 9h1M59 9h3M63 9h1M67 9h3M12 10h1M12 11h1" />
<path stroke="#434343" d="M0 12h1M74 12h1M1 13h73" />
<path stroke="#cccccc" d="M1 12h73" />
</svg>
      
      
      
   </div>
</div>
:root {
   --pixel-size: 2px;
   --grid-cell: calc( var(--pixel-size) * 16);
   --bg: #9fa7e4;
}
@media( min-width: 700px ) {
   :root {
      --pixel-size: 3px;
   }
}
@media( min-width: 1000px ) {
   :root {
      --pixel-size: 4px;
   }
}
/* @media( min-width: 1000px ) {
   :root {
      --pixel-size: 5px;
   }
} */

html, body {
   height: 100%;
}

body {
   background: var(--bg);
   display: flex;
   align-items: center;
   justify-content: center;
}

.pixel-art {
   image-rendering: pixelated;
}

.frame {
   /* This is just for Pen decoration */
   width: calc(var(--pixel-size) * 160);
   height: calc(var(--pixel-size) * 144);
   outline: var(--pixel-size) solid #fff;
   z-index:1; 
   position:relative;
}

.camera {
   width: calc(var(--pixel-size) * 160);
   height: calc(var(--pixel-size) * 144);
   overflow: hidden;
   background: #61ddf7;
   position:relative;
}

.map {
   image-rendering: pixelated;
   background-image: url("https://assets.codepen.io/21542/CameraDemoMap.png");
   background-size: 100%;
   width: calc(13 * var(--grid-cell));
   height: calc(10 * var(--grid-cell));
   position: relative;
}

.character {
   width: calc( var(--grid-cell)* 2 );
   height: calc( var(--grid-cell)* 2 );
   position: absolute;
   overflow:hidden;
}

.shadow {
   width: calc( var(--grid-cell)* 2 );
   height: calc( var(--grid-cell)* 2 );
   position: absolute;
   left:0;
   top:0;
   background: url("https://assets.codepen.io/21542/DemoRpgCharacterShadow.png") no-repeat no-repeat;
   background-size: 100%;
}

.character_spritesheet {
   position: absolute;
   background: url("https://assets.codepen.io/21542/DemoRpgCharacter.png") no-repeat no-repeat;
   background-size: 100%;
   width: calc( var(--grid-cell)* 8 );
   height: calc( var(--grid-cell)* 8 );
}

.character[facing="right"] .character_spritesheet {
   background-position-y: calc( var(--pixel-size) * -32 );
}
.character[facing="up"] .character_spritesheet {
   background-position-y: calc( var(--pixel-size) * -64 );
}
.character[facing="left"] .character_spritesheet {
   background-position-y: calc( var(--pixel-size) * -96 );
}
.character[walking="true"] .character_spritesheet {
   animation: walkAnimation 0.6s steps(4) infinite; 
}

@keyframes walkAnimation {
  from {
    transform: translate3d(0%,0%,0);
  }
  to {
    transform: translate3d(-100%,0%,0);
  }
}

.dpad {
   position:absolute;
   right: calc(var(--pixel-size) * 2);
   bottom: calc(var(--pixel-size) * 2);
   width: calc(var(--pixel-size) * 37);
   height: calc(var(--pixel-size) * 38);
}
.dpad-button {
   appearance:none;
   outline:0;
   border:0;
   background:transparent;
   padding:0;
   cursor:pointer;
}
.dpad-button svg {
   display:block;
   height: calc(var(--pixel-size) * 13);
}

.dpad-button.pressed .Arrow_arrow-inset { stroke:#07c2cc; }
.dpad-button.pressed .Arrow_arrow-body { stroke:#17dfea; }

.dpad-up {
   position: absolute;
   left: calc(var(--pixel-size) * 12);
   top:0;
}
.dpad-down {
   position: absolute;
   bottom:var(--pixel-size);
   left: calc(var(--pixel-size) * 12);
}
.dpad-left {
   position: absolute;
   top: calc(var(--pixel-size) * 12);
   left:0;
}
.dpad-right {
   position: absolute;
   top: calc(var(--pixel-size) * 12);
   right:0;
}

.dpad {
   user-select:none;
}


.corner_topleft,
.corner_topright,
.corner_bottomleft,
.corner_bottomright {
   position: absolute;
   width: var(--pixel-size);
   height: var(--pixel-size);
   background: var(--bg);
   z-index:2;
}

.corner_topleft {
   top: calc(var(--pixel-size) * -1);
   left: calc(var(--pixel-size) * -1);
}
.corner_topright {
   top: calc(var(--pixel-size) * -1);
   right: calc(var(--pixel-size) * -1);
}
.corner_bottomleft {
   bottom: calc(var(--pixel-size) * -1);
   left: calc(var(--pixel-size) * -1);
}
.corner_bottomright {
   bottom: calc(var(--pixel-size) * -1);
   right: calc(var(--pixel-size) * -1);
}

.headline {
   position:absolute;
   top:calc(var(--pixel-size) * 2);
   right:calc(var(--pixel-size) * 2);
   width: calc(var(--pixel-size) * 75)
}
var character = document.querySelector(".character");
var map = document.querySelector(".map");

//start in the middle of the map
var x = 90;
var y = 34;
var held_directions = []; //State of which arrow keys we are holding down
var speed = 1; //How fast the character moves in pixels per frame

const placeCharacter = () => {
   
   var pixelSize = parseInt(
      getComputedStyle(document.documentElement).getPropertyValue('--pixel-size')
   );
   
   const held_direction = held_directions[0];
   if (held_direction) {
      if (held_direction === directions.right) {x += speed;}
      if (held_direction === directions.left) {x -= speed;}
      if (held_direction === directions.down) {y += speed;}
      if (held_direction === directions.up) {y -= speed;}
      character.setAttribute("facing", held_direction);
   }
   character.setAttribute("walking", held_direction ? "true" : "false");
   
   //Limits (gives the illusion of walls)
   var leftLimit = -8;
   var rightLimit = (16 * 11)+8;
   var topLimit = -8 + 32;
   var bottomLimit = (16 * 7);
   if (x < leftLimit) { x = leftLimit; }
   if (x > rightLimit) { x = rightLimit; }
   if (y < topLimit) { y = topLimit; }
   if (y > bottomLimit) { y = bottomLimit; }
   
   
   var camera_left = pixelSize * 66;
   var camera_top = pixelSize * 42;
   
   map.style.transform = `translate3d( ${-x*pixelSize+camera_left}px, ${-y*pixelSize+camera_top}px, 0 )`;
   character.style.transform = `translate3d( ${x*pixelSize}px, ${y*pixelSize}px, 0 )`;  
}


//Set up the game loop
const step = () => {
   placeCharacter();
   window.requestAnimationFrame(() => {
      step();
   })
}
step(); //kick off the first step!



/* Direction key state */
const directions = {
   up: "up",
   down: "down",
   left: "left",
   right: "right",
}
const keys = {
   38: directions.up,
   37: directions.left,
   39: directions.right,
   40: directions.down,
}
document.addEventListener("keydown", (e) => {
   var dir = keys[e.which];
   if (dir && held_directions.indexOf(dir) === -1) {
      held_directions.unshift(dir)
   }
})

document.addEventListener("keyup", (e) => {
   var dir = keys[e.which];
   var index = held_directions.indexOf(dir);
   if (index > -1) {
      held_directions.splice(index, 1)
   }
});



/* BONUS! Dpad functionality for mouse and touch */
var isPressed = false;
const removePressedAll = () => {
   document.querySelectorAll(".dpad-button").forEach(d => {
      d.classList.remove("pressed")
   })
}
document.body.addEventListener("mousedown", () => {
   console.log('mouse is down')
   isPressed = true;
})
document.body.addEventListener("mouseup", () => {
   console.log('mouse is up')
   isPressed = false;
   held_directions = [];
   removePressedAll();
})
const handleDpadPress = (direction, click) => {   
   if (click) {
      isPressed = true;
   }
   held_directions = (isPressed) ? [direction] : []
   
   if (isPressed) {
      removePressedAll();
      document.querySelector(".dpad-"+direction).classList.add("pressed");
   }
}
//Bind a ton of events for the dpad
document.querySelector(".dpad-left").addEventListener("touchstart", (e) => handleDpadPress(directions.left, true));
document.querySelector(".dpad-up").addEventListener("touchstart", (e) => handleDpadPress(directions.up, true));
document.querySelector(".dpad-right").addEventListener("touchstart", (e) => handleDpadPress(directions.right, true));
document.querySelector(".dpad-down").addEventListener("touchstart", (e) => handleDpadPress(directions.down, true));

document.querySelector(".dpad-left").addEventListener("mousedown", (e) => handleDpadPress(directions.left, true));
document.querySelector(".dpad-up").addEventListener("mousedown", (e) => handleDpadPress(directions.up, true));
document.querySelector(".dpad-right").addEventListener("mousedown", (e) => handleDpadPress(directions.right, true));
document.querySelector(".dpad-down").addEventListener("mousedown", (e) => handleDpadPress(directions.down, true));

document.querySelector(".dpad-left").addEventListener("mouseover", (e) => handleDpadPress(directions.left));
document.querySelector(".dpad-up").addEventListener("mouseover", (e) => handleDpadPress(directions.up));
document.querySelector(".dpad-right").addEventListener("mouseover", (e) => handleDpadPress(directions.right));
document.querySelector(".dpad-down").addEventListener("mouseover", (e) => handleDpadPress(directions.down));







External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.