如何使画布上下文跟随玩家?

huangapple go评论53阅读模式
英文:

How do I make the canvas context follow the player?

问题

以下是你的代码的翻译部分,不包括代码本身:

I am building a game using JavaScript and canvases, and I want to implement a viewport to display a larger game world. I have already created a canvas element and loaded some stationary objects and a player object that can be moved around using keyboard input.

Specifically, I am looking for code that will allow me to define a viewport size and position, and to update the game world position based on the player's movement, so that the player remains centered in the viewport as they move around.

Here is my current code. Note, I am very new to js:

    // Get the canvas element and its context
    const canvas = document.createElement("canvas");
    document.body.append(canvas);
    const ctx = canvas.getContext("2d");

    // Player coords and update method that moves the player based on input
    const player = {
      x: 0,
      y: 0,
      vel: 1,
      update(){
        if(key.left) this.x -= this.vel;
        if(key.right) this.x += this.vel;
        if(key.up) this.y -= this.vel;
        if(key.down) this.y += this vel;
      }
    };
    
    // Keys pressed (Used for movement)
    const key = {
      left: false,
      right: false,
      up: false,
      down: false
    }
    
    // Get Input
    window.addEventListener('keydown', (e) => {
      switch(e.code){
        case "KeyW":
          key["up"] = true;
          break;
        case "KeyS":
          key["down"] = true;
          break;
        case "KeyA":
          key["left"] = true;
          break;
        case "KeyD":
          key["right"] = true;
          break;
      }
    });
    
    // Relieve Input
    window.addEventListener('keyup', (e) => {
      switch(e.code){
        case "KeyW":
          key["up"] = false;
          break;
        case "KeyS":
          key["down"] = false;
          break;
        case "KeyA":
          key["left"] = false;
          break;
        case "KeyD":
          key["right"] = false;
          break;
      }
    });
    
    function update(){
      ctx.clearRect(0,0,canvas.width, canvas.height);
      // Draw stationary objects
      ctx.fillRect(100, 100, 10, 10);
      ctx.fillRect(100, 120, 10, 10);
    
      // Draw Player and update position
      player.update();
      ctx.fillRect(player.x, player.y, 10, 10);
      requestAnimationFrame(update);
    }
    update();

I have tried saving the context and translating then restoring. That did not work; however, I was expecting the player to stay centered on the screen. Here is the code I tried:

    function update(){
      ctx.clearRect(0,0,canvas.width, canvas.height);
      // Draw stationary objects
      ctx.fillRect(100, 100, 10, 10);
      ctx.fillRect(100, 120, 10, 10);

      // Draw Player and update position
      ctx.save();
      ctx.translate(player.x, player.y);
      player.update();
      ctx.fillRect(canvas.width/2 - 5, canvas.height/2 - 5, 10, 10);
      ctx.restore();
      requestAnimationFrame(update);
    }
英文:

I am building a game using JavaScript and canvases, and I want to implement a viewport to display a larger game world. I have already created a canvas element and loaded some stationary objects and a player object that can be moved around using keyboard input.

However, I am unsure how to implement a viewport to show only a portion of the game world and allow the player to move around it. Can someone provide an example of how to implement a viewport using JavaScript and canvases, based on the provided code?

Specifically, I am looking for code that will allow me to define a viewport size and position, and to update the game world position based on the player's movement, so that the player remains centered in the viewport as they move around.

Here is my current code. Note, I am very new to js:

    // Get the canvas element and its context
    const canvas = document.createElement("canvas");
    document.body.append(canvas);
    const ctx = canvas.getContext("2d");

    // Player coords and update method that moves the player based on input
    const player = {
      x: 0,
      y: 0,
      vel: 1,
      update(){
        if(key.left) this.x -= this.vel;
        if(key.right) this.x += this.vel;
        if(key.up) this.y -= this.vel;
        if(key.down) this.y += this.vel;
      }
    };





    // Keys pressed (Used for movement)
    const key = {
      left: false,
      right: false,
      up: false,
      down: false
    }
    
    
    // Get Input
    window.addEventListener('keydown', (e) =>
    {
      switch(e.code){
        case "KeyW":
          key["up"] = true;
          break;
        case "KeyS":
          key["down"] = true;
          break;
        case "KeyA":
          key["left"] = true;
          break;
        case "KeyD":
          key["right"] = true;
          break;
      }
    });

    // Relieve Input
    window.addEventListener('keyup', (e) =>
    {
      switch(e.code){
        case "KeyW":
          key["up"] = false;
          break;
        case "KeyS":
          key["down"] = false;
          break;
        case "KeyA":
          key["left"] = false;
          break;
        case "KeyD":
          key["right"] = false;
          break;
      }
    });

    function update(){
      ctx.clearRect(0,0,canvas.width, canvas.height);
      // Draw stationary objects
      ctx.fillRect(100, 100, 10, 10);
      ctx.fillRect(100, 120, 10, 10);

      // Draw Player and update position
      player.update();
      ctx.fillRect(player.x, player.y, 10, 10);
      requestAnimationFrame(update);
    }
    update();

I have tried saving the the context and translating then restoring. That didn't not work however, I was expecting the player to stay centered in the screen. Here is the code I tried:

    function update(){
      ctx.clearRect(0,0,canvas.width, canvas.height);
      // Draw stationary objects
      ctx.fillRect(100, 100, 10, 10);
      ctx.fillRect(100, 120, 10, 10);

      // Draw Player and update position
      ctx.save();
      ctx.translate(player.x, player.y);
      player.update();
      ctx.fillRect(canvas.width/2 - 5, canvas.height/2 - 5, 10, 10);
      ctx.restore();
      requestAnimationFrame(update);
    }

答案1

得分: 0

大多数游戏中的2D场景实现都使用父/子树,其中场景是顶层父级(在位置上对对象进行父级设置实际上是使它们依赖于另一个对象的位置。

一旦你有了这个,你可以通过将每个对象都添加到相同的父级(例如,场景),并将该父级移动来模拟相机来轻松移动视口。

以下是一些伪代码,以了解这种系统背后的思想。

//伪代码,请不要直接复制粘贴
// 创建一个所有游戏对象都将继承的类;它将负责管理位置。

class GameObject {
    position: {x: 0, y: 0}
    parent: null;
    children: [];
    function setPosition(pX, pY) { //设置对象在父级内的位置
        position.x = pX;
        position.y = pY;
    }
    //这是我们的父/子树的核心
    function addChild(pGameObject) {
        children.push(pGameObject);
        pGameObject.parent = this;
    }
    function removeChild() //负责更新父级/子级变量

    function toGlobalCoordinates() { //允许我们获取全局坐标(在画布上的坐标)
       let lParent = this.parent;
       let globalPos = {x: 0, y: 0}
       //向上遍历父级树,当没有更多的父级时,使用默认值:类中的 parent = null
       while(lParent) {
           globalPos.x += lParent.position.x;
           globalPos.y += lParent.position.y;
           lParent = lParent.parent;
       }
       globalPos.x += this.position.x;
       globalPos.y += this.position.y;
       return globalPos;
    }
 
    function draw(pCtx) { //我们传递画布ctx以便绘制在上面
        //使用全局位置来绘制对象
        let drawingPos = this.toGlobalCoordinates();
        pCtx.fillRect(drawingPos.x, drawingPos.y, 100, 100);
    }
}

然后,我们有其他类,如玩家类,我们需要处理渲染。

//从GameObject继承以便受益于位置及其方法
class Player extends GameObject {
    methods() ... //实现特定于玩家的方法
}

//使用场景作为基本游戏对象(顶级父级)
let scene  = new GameObject();
let player = new Player();
let stationnaryObject1 = new GameObject()

scene.addChild(player)
scene.addChild(stationnaryObject1)

//requestAnimationFrame上调用的函数
update() {
   //使用clearRect清除画布

   //在按键按下时,移动玩家,移动场景相反方向以保持焦点在玩家身上
   if(keypress) {
     player.setPosition(player.position.x + newX, player.position.x + newY)
     scene.setPosition(scene.position.x - newX, scene.position.x - newY)
   }
   
   //使用新位置绘制对象
   scene.draw(ctx);
   player.draw(ctx);
}

这更或多是处理制作摄像机的基本方法。

如果你正在寻找一种非常强大且易于学习的JS库来处理2D显示在WebGL和画布上(不再需要手动绘制),你应该查看PixiJS

要了解我提到的场景图/树,请参考:https://pixijs.io/guides/basics/scene-graph.html,类似的树在Flash游戏中也被使用。

简而言之:制作摄像机即改变所有对象的位置,使它们依附于摄像机。

希望你开发游戏时能玩得开心!

英文:

Most 2D scenes implementations in games work with a parent/child tree where the scene is the top parent (parenting an object position-wise is simply making them dependent of another object's position.)

Once you have that, you can easily move the viewport by adding every object to the same parent (for exemple, the scene) containing all your objects, and move that parent to simulate a camera.

Here's some pseudo code to get the ideas behind such a system.

//Pseudo code, don't simply copy-paste it
// Make a class from which all the objects in the game will inherit; it'll take care of managing the position.

class GameObject {
    position: {x: 0, y: 0}
    parent: null;
    children: [];
    function setPosition(pX, pY) { //Sets the position within the parent
        position.x = pX;
        position.y = pY;
    }
    //This is the core of our parent/child tree
    function addChild(pGameObject) {
        children.push(pGameObject);
        pGameObject.parent = this;
    }
    function removeChild() //take care of updating the parent/children vars

    function toGlobalCoordinates() { //Allows us to get the global coordinates (= coordinates on the canvas)
       let lParent = this.parent;
       let globalPos = {x: 0, y: 0}
       //Goes up the parents tree, breaks when there's no more parent thanks to the default: parent = null in the class
       while(lParent) {
           globalPos.x += lParent.position.x;
           globalPos.y += lParent.position.y;
           lParent = lParent.parent;
       }
       globalPos.x += this.position.x;
       globalPos.y += this.position.y;
       return globalPos;
    }
 
    function draw(pCtx) { //We pass the canvas ctx to be able to draw on it
        //Use the global position to draw your object
        let drawingPos = this.toGlobalCoordinates();
        pCtx.fillRect(drawingPos.x, drawingPos.y, 100, 100);
    }
}

Then, we have the other classes like the Player and we have to take care of the rendering

//extends from GameObject to benefit from the position and its methods
class Player extends GameObject {
methods() ... //Implement methods specific to the player
}
//Use the scene as the base gameobject (our top parent)
let scene  = new GameObject();
let player = new Player();
let stationnaryObject1 = new GameObject()
scene.addChild(player)
scene.addChild(stationnaryObject1)
//The function called on requestAnimationFrame
update() {
//clearRect to clean the canvas then
//On key press, move the player, move the scene in the opposite direction to keep the keep focus on the player
if(keypress) {
player.setPosition(player.position.x + newX, player.position.x + newY)
scene.setPosition(scene.position.x - newX, scene.position.x - newY)
}
//Draw the objects with their new position
scene.draw(ctx);
player.draw(ctx);
}

That's more or less a basic way to handle being able to make a camera (aka move the viewport in the gameworld).

You should take a look at PixiJS if you're looking for a very robust way to handle that, and a lot more.
It's a very powerful and easy to learn JS library to handle 2D display on WebGL & canvas (no more drawing yourself !)

To learn more about the Scene graph/tree I talked about : https://pixijs.io/guides/basics/scene-graph.html the same kind of tree was used in Flash games for example.

TLDR : Making a camera is changing a position all objects' positions are attached to.

I hope you'll have fun developping games !

huangapple
  • 本文由 发表于 2023年3月31日 22:44:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/75899847.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定