{"id":1126,"date":"2025-10-27T08:00:00","date_gmt":"2025-10-27T07:00:00","guid":{"rendered":"https:\/\/jorgeturiel.es\/?p=1126"},"modified":"2025-09-28T20:48:43","modified_gmt":"2025-09-28T19:48:43","slug":"ghosts-n-goblins-y-cge-parte-3","status":"publish","type":"post","link":"https:\/\/jorgeturiel.es\/?p=1126","title":{"rendered":"Ghosts \u2018n Goblins y CGE. Parte 3"},"content":{"rendered":"\n<p>En esta tercera entrega, vamos a ver como mover el personaje por el escenario, as\u00ed como hacer que la c\u00e1mara lo siga, y que ninguno de los dos se salga del escenario.<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>Para que la c\u00e1mara siga el personaje, debemos cambiar el enfoque en nuestro c\u00f3digo.Cuando se pulse una tecla, se desplazar\u00e1 el personaje, y despu\u00e9s situaremos la c\u00e1mara d\u00f3nde est\u00e9 el personaje.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Organizando el c\u00f3digo (Refactoring)<\/h2>\n\n\n\n<p>C\u00f3mo el c\u00f3digo empieza a ser cada vez m\u00e1s grande, y con el objetivo de hacerlo m\u00e1s legible, antes de continuar vamos a hacer algunos cambios en el c\u00f3digo.<\/p>\n\n\n\n<p>En la parte privada de TViewMain declara las siguientes funciones para mover el personaje:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" >    function MoveLeft: boolean;\n    function MoveRight: boolean;\n    function MoveDown: boolean;\n    function Jump: boolean;\n    function PlayerOnGround: boolean;\n    function FireWeapon: boolean;  <\/pre><\/div>\n\n\n\n<p>Tras declararlas, si pulsas <em>shift+c <\/em><em>sobre cada una de ellas, el IDE Lazarus, c<\/em>rear\u00e1 el c\u00f3digo correspondiente. Cada una de estas funciones se ocupar\u00e1 de realizar una acci\u00f3n sobre el personaje. En cada una de ellas, se evaluar\u00e1 si se ha pulsado la tecla correspondiente a la acci\u00f3n, en tal caso devolver\u00e1 <em>TRUE, sino devolver\u00e1 <\/em>FALSE. Adem\u00e1s cuando la tecla es pulsada, se realizar\u00e1n ciertas operaciones sobre el personaje.<\/p>\n\n\n\n<p>Veamos el c\u00f3digo de las funciones que se encargan de mover el personaje hacia la derecha y la izquierda.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" >\/\/Personaje hacia la izquierda\nfunction TViewMain.MoveLeft: boolean;\nbegin\n  Result := False;\n  if Container.Pressed[keyArrowLeft] then\n  begin\n    if Arthur.Rotation.W = 0 then\n    begin\n      Arthur.Rotation := Vector4(0, 1, 0, Deg(180));\n    end;\n    Result := True;\n  end;\nend;\n\n\/\/Personaje hacia la derecha\nfunction TViewMain.MoveRight: boolean;\nbegin\n  Result := False;\n  if Container.Pressed[keyArrowRight] then\n  begin\n    if Arthur.Rotation.W &lt;&gt; 0 then\n    begin\n      Arthur.Rotation := Vector4(0, 1, 0, 0);\n    end;\n    Result := True;\n  end;\nend;            \n<\/pre><\/div>\n\n\n\n<p><br>En estas funciones, en el caso que se pulse la tecla correspondiente, aparte de retornar <em>TRUE<\/em>, se comprobar\u00e1 si el personaje se le ha aplicado alguna rotaci\u00f3n. Por defecto el personaje, tal como creamos el sprite, est\u00e1 mirando a la izquierda.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"748\" height=\"190\" src=\"https:\/\/jorgeturiel.es\/wp-content\/uploads\/2025\/09\/image.png\" alt=\"\" class=\"wp-image-1128\" srcset=\"https:\/\/jorgeturiel.es\/wp-content\/uploads\/2025\/09\/image.png 748w, https:\/\/jorgeturiel.es\/wp-content\/uploads\/2025\/09\/image-300x76.png 300w\" sizes=\"auto, (max-width: 748px) 100vw, 748px\" \/><figcaption class=\"wp-element-caption\">Personaje a 0\u00ba<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Observa que su \u00e1ngulo de rotaci\u00f3n \u201c<em>Angle(W)\u201d<\/em> es cero, esto indica que el sprite del personaje \u201cest\u00e1 tal cual\u201d. Ya adem\u00e1s no se aplica sobre ning\u00fan eje (aunque se aplicara, al ser el \u00e1ngulo igual a cero no se producir\u00eda ninguna rotaci\u00f3n).<\/p>\n\n\n\n<p>Si, cambiamos el valor del \u00e1ngulo a 180 grados, e indicamos que la rotaci\u00f3n sea sobre el eje Y, cambiando su valor de cero a uno, el personaje rotar\u00e1 180 grados sobre su eje Y.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"689\" height=\"162\" src=\"https:\/\/jorgeturiel.es\/wp-content\/uploads\/2025\/09\/image-1.png\" alt=\"\" class=\"wp-image-1129\" srcset=\"https:\/\/jorgeturiel.es\/wp-content\/uploads\/2025\/09\/image-1.png 689w, https:\/\/jorgeturiel.es\/wp-content\/uploads\/2025\/09\/image-1-300x71.png 300w\" sizes=\"auto, (max-width: 689px) 100vw, 689px\" \/><figcaption class=\"wp-element-caption\">Personaje rotada 180\u00ba<\/figcaption><\/figure>\n\n\n\n<p>Es operaci\u00f3n que hemos hecho sobre las propiedades del personaje, es lo que se realiza por c\u00f3digo en estas funciones.<\/p>\n\n\n\n<p>Ten en cuenta que los \u00e1ngulos, en CGE, son en radianes. Por eso se usa la funci\u00f3n <em>Deg<\/em> para convertir un valor en grados a radianes. Esta funci\u00f3n est\u00e1 la unidad <em>CastleUtils<\/em>, por lo cual debemos a\u00f1adirla para poder usarla.<\/p>\n\n\n\n<p>As\u00ed que justo al empezar la implementaci\u00f3n a\u00f1ade esta unidad a la clausula<em> uses<\/em>.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"281\" height=\"69\" src=\"https:\/\/jorgeturiel.es\/wp-content\/uploads\/2025\/09\/image-2.png\" alt=\"\" class=\"wp-image-1130\"\/><figcaption class=\"wp-element-caption\">Unidades a\u00f1adidas en la secci\u00f3n implementaci\u00f3n<\/figcaption><\/figure>\n<\/div>\n\n\n<p>La funci\u00f3n <em>PlayerOnGround<\/em>, devuelve el estado de la variable <em>OnGround<\/em>. Define esta variable en la parte privada de <em>TViewMain<\/em>.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" >function TViewMain.PlayerOnGround: boolean;\nbegin\n  Result := OnGround;\nend;<\/pre><\/div>\n\n\n\n<p><br>Las funciones restantes, simplemente devolver\u00e1n si la tecla correspondiente ha sido pulsada.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" >function TViewMain.MoveDown: boolean;\nbegin\n  Result := False;\n  if Container.Pressed[keyDown] then\n  begin\n    Result := True;\n  end;\nend;\n\nfunction TViewMain.Jump: boolean;\nbegin\n  Result := False;\n  if Container.Pressed[keyCtrl] then\n  begin\n    Result := True;\n  end;\nend;\n\nfunction TViewMain.PlayerOnGround: boolean;\nbegin\n  Result := OnGround;\nend;\n\nfunction TViewMain.FireWeapon: boolean;\nbegin\n  Result := False;\n  if Container.Pressed[keySpace] then\n  begin\n    Result := True;\n  end;\nend;<\/pre><\/div>\n\n\n\n<p><br>La variable <em>OnGround<\/em>, indicar\u00e1 si el personaje est\u00e1 sobre el suelo o no. Necesitamos saber d\u00f3nde est\u00e1 para activar o desactivar ciertas acciones. Su estado depender\u00e1 si el personaje colisiona con el suelo, o deja de colisionar con este. As\u00ed que definiremos dos eventos para el suelo, <em>OnCollisionEnter<\/em>, y <em>OnColisionExit,<\/em> esto lo haremos evento <em>Start<\/em>.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" title=\"Asignaci\u00f3n de eventos de colisi\u00f3n al suelo\" >procedure TViewMain.Start;\nbegin\n  inherited;\n  {$IFDEF FPC}\n   Suelo1.RigidBody.OnCollisionEnter:=@Suelo1CollisionEnter;\n   Suelo1.RigidBody.OnCollisionExit:=@Suelo1CollisionExit;\n  {$ELSE}\n  Suelo1.RigidBody.OnCollisionEnter := Suelo1CollisionEnter;\n  Suelo1.RigidBody.OnCollisionExit := Suelo1CollisionExit;\n  {$ENDIF}\n  OnGround := True;\nend; <\/pre><\/div>\n\n\n\n<p>Para definir autom\u00e1ticamente los procedimientos asignados a cada evento, puedes pulsar <em>CLTR+C<\/em>, sobre cada uno de ellos, para que se genere la c\u00f3digo correspondiste.<\/p>\n\n\n\n<p>Recuerda que debes definir en la secci\u00f3n <em>Published<\/em> de <em>TmainView<\/em>, los elementos que usemos en el c\u00f3digo.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" title=\"Declaraci\u00f3n componentes usados en CGE\" >published\n    { Components designed using CGE editor.\n      These fields will be automatically initialized at Start. }\n    LabelFps: TCastleLabel;\n    Navigation2D1: TCastle2DNavigation;\n    Arthur: TCastleScene;\n    Suelo1: TCastleBox;     <\/pre><\/div>\n\n\n\n<p><br>Cuando entre en colisi\u00f3n alg\u00fan objeto con el suelo, se ejecutar\u00e1 <em>Suelo1CollisionEnter<\/em>, as\u00ed que dentro de este evento, comprobaremos si el objeto que ha colisionado es nuestro personaje, en tal caso pondremos la variable <em>OnGround<\/em> a <em>TRUE<\/em>.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" title=\"Evento de entrada en colisi\u00f3n\" >procedure TViewMain.Suelo1CollisionEnter(\n  const CollisionDetails: TPhysicsCollisionDetails);\nbegin\n  OnGround := CollisionDetails.OtherTransform.Name = Arthur.Name;\nend; <\/pre><\/div>\n\n\n\n<p>De la misma manera, cuando un objeto deje de colisionar con el suelo, se ejecutar\u00e1 <em>Suelo1CollisionExit<\/em>, si el objeto que sale de la colisi\u00f3n es el personaje, pondremos la variable <em>OnGround<\/em> a <em>False<\/em>.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" title=\"Evento salida de colisi\u00f3n\" >procedure TViewMain.Suelo1CollisionExit(\n  const CollisionDetails: TPhysicsCollisionDetails);\nbegin\n  OnGround := not (CollisionDetails.OtherTransform.Name = Arthur.Name);\nend;  <\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Reescribir el evento Update<\/h2>\n\n\n\n<p>El objetivo de los cambios que vamos a realizar es simplificar el evento Update.<\/p>\n\n\n\n<p>Lo primero que hacemos en guardar en una variable la velocidad actual del personaje.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" title=\"Velocidad del personaje\" > vel := Arthur.RigidBody.LinearVelocity;<\/pre><\/div>\n\n\n\n<p><br>Luego comprobar si se ha pulsado si el personaje debe ir a la izquierda o la derecha, y si est\u00e1 en el suelo. De esta manera si el personaje no estuviera en el suelo, no se le aplicar\u00eda ninguna velocidad en el X, ya que queremos que se desplaza solamente cuando est\u00e9 en el suelo.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" >if MoveRight and PlayerOnGround then\n  begin\n    vel.x := 1500 * SecondsPassed;\n  end;\n  if MoveLeft and PlayerOnGround then\n  begin\n    vel.X := -1500 * SecondsPassed;\n  end;<\/pre><\/div>\n\n\n\n<p>Lo siguiente, es comprobar si se ha pulsado la tecla saltar( <em>jump<\/em>), y si est\u00e1 en el suelo, se aplica una velocidad en eje Y, que es el eje vertical.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" >if Jump and PlayerOnGround then\n  begin\n    vel.y := 1500 * SecondsPassed;\n  end;<\/pre><\/div>\n\n\n\n<p>Por \u00faltimo comprobamos que sino se ha pulsado ninguna tecla , y el personaje est\u00e1 en el suelo, ajustamos la velocidad del eje X a cero.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" >if (MoveLeft = False) and (MoveRight = False) and (PlayerOnGround = True) then\n  begin\n    vel.x := 0;\n  end;<\/pre><\/div>\n\n\n\n<p>Para terminar, aplicamos la velocidad al personaje.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" >Arthur.RigidBody.LinearVelocity := Vel;<\/pre><\/div>\n\n\n\n<p>Ahora debemos actualizar la animaci\u00f3n del personaje, en funci\u00f3n de su velocidad y si est\u00e1 en el suelo o no.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" >  if (Arthur.RigidBody.LinearVelocity.X = 0) or (not PlayerOnGround) then\n  begin\n    Arthur.StopAnimation();\n  end\n  else\n  begin\n    if Arthur.CurrentAnimation = nil then\n      Arthur.PlayAnimation('walk', True);\n  end;<\/pre><\/div>\n\n\n\n<p>Por \u00faltimo, guardamos en la variable llamada <em>PosicionCamara<\/em>, un vector que contiene en el eje X la posici\u00f3n del personaje, y en la coordenada Y, la posici\u00f3n de la c\u00e1mara. Y eso ser\u00e1 posici\u00f3n de la c\u00e1mara.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" > PosicionCamara := Vector2(Arthur.TranslationXY.X, Navigation2D1.Camera.TranslationXY.Y);\n Navigation2D1.Camera.TranslationXY := PosicionCamara;<\/pre><\/div>\n\n\n\n<p>Si probamos el juego ahora, veremos que, tanto,el jugador como la c\u00e1mara se salen del escenario. Para ello, vamos a limitar el movimiento de ambos. Definimos al inicio de la unidad, una clase llamada TLevelBounds.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" >type\n\n  { TLevelBounds }\n\n  TLevelBounds = class(TComponent)\n  public\n    Left: single;\n    Right: single;\n    Top: single;\n    Down: single;\n    constructor Create(AOwner: TComponent); override;\n  end;       <\/pre><\/div>\n\n\n\n<p>Esta nos contendr\u00e1 4 propiedades, las cuales representaran los l\u00edmites del escenario.<\/p>\n\n\n\n<p>En su constructor definimos los l\u00edmites.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" >constructor TLevelBounds.Create(AOwner: TComponent);\nbegin\n  inherited Create(AOwner);\n  Left := -1780;\n  Right := 1780;\n  Top := 104;\n  Down := -104; \nend;  \n<\/pre><\/div>\n\n\n\n<p>Y en el evento <em>OnStart<\/em>, creamos el objeto.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" >procedure TViewMain.Start;\nbegin\n  inherited;\n  LevelBounds := TlevelBounds.Create(Self); \/\/L\u00edmites del escenario\n  {$IFDEF FPC}\n   Suelo1.RigidBody.OnCollisionEnter:=@Suelo1CollisionEnter;\n   Suelo1.RigidBody.OnCollisionExit:=@Suelo1CollisionExit;\n  {$ELSE}\n  Suelo1.RigidBody.OnCollisionEnter := Suelo1CollisionEnter;\n  Suelo1.RigidBody.OnCollisionExit := Suelo1CollisionExit;\n  {$ENDIF}\n  OnGround := True;\nend;  <\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Usando logging<\/h2>\n\n\n\n<p>Para conocer d\u00f3nde est\u00e1 el jugador en cualquier momento, y as\u00ed conocer los l\u00edmites del escenario, use la t\u00e9cnica del logging. Esto es que el juego muestre informaci\u00f3n en cualquier momento.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"816\" height=\"321\" src=\"https:\/\/jorgeturiel.es\/wp-content\/uploads\/2025\/09\/image-3.png\" alt=\"\" class=\"wp-image-1138\" srcset=\"https:\/\/jorgeturiel.es\/wp-content\/uploads\/2025\/09\/image-3.png 816w, https:\/\/jorgeturiel.es\/wp-content\/uploads\/2025\/09\/image-3-300x118.png 300w, https:\/\/jorgeturiel.es\/wp-content\/uploads\/2025\/09\/image-3-768x302.png 768w\" sizes=\"auto, (max-width: 816px) 100vw, 816px\" \/><figcaption class=\"wp-element-caption\">Ventana de salida<\/figcaption><\/figure>\n<\/div>\n\n\n<p>En la parte de abajo del editor, existe la pesta\u00f1a <em>Output<\/em>, est\u00e1 muestra informaci\u00f3n cuando el juego se inicia. Y tambi\u00e9n puede mostrar informaci\u00f3n que nuestro programa quiera.<\/p>\n\n\n\n<p>A\u00f1ade la unidad castlelog, justo debajo de la implementaci\u00f3n.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"298\" height=\"69\" src=\"https:\/\/jorgeturiel.es\/wp-content\/uploads\/2025\/09\/image-4.png\" alt=\"\" class=\"wp-image-1139\"\/><figcaption class=\"wp-element-caption\">Clausula uses en la secci\u00f3n de implementaci\u00f3n.<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Ahora en el evento <em>Press,<\/em> escribe lo siguiente<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" >if Event.IsKey(keyEnter) then\n\nbegin\n\nWritelnLog(Arthur.TranslationXY.ToString);\n\nExit(true);\n\nend;<\/pre><\/div>\n\n\n\n<p>Este c\u00f3digo mostrar\u00e1 por la ventana <em>output<\/em> la posici\u00f3n de Arthur.<\/p>\n\n\n\n<p>Ahora, podemos limitar la posici\u00f3n del personaje con las siguiente instrucciones, de nuevo en el evento <em>Update<\/em>.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" >if Arthur.TranslationXY.X &lt; LevelBounds.Left then\n  begin\n    \/\/Detener el movimiento\n    Arthur.RigidBody.LinearVelocity := Vector3(0, 0, 0);\n    Arthur.TranslationXY := Vector2(LevelBounds.Left, Arthur.TranslationXY.Y);\n  end;\n\n  if Arthur.TranslationXY.X &gt; LevelBounds.Right then\n  begin\n    \/\/Detener el movimiento\n    Arthur.RigidBody.LinearVelocity := Vector3(0, 0, 0);\n    Arthur.TranslationXY := Vector2(LevelBounds.Right, Arthur.TranslationXY.Y);\n  end;<\/pre><\/div>\n\n\n\n<p><br>Ahora, el personaje no se sale de escenario, pero la c\u00e1mara si, ya que siempre lo mantiene en el centro. Para evitar este efecto, vamos limitar el movimiento de la c\u00e1mara, de manera que si su centro, m\u00e1s la mitad de su ancho del campo de visi\u00f3n, es menor o mayor a los l\u00edmites, corregiremos su posici\u00f3n.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"918\" height=\"598\" src=\"https:\/\/jorgeturiel.es\/wp-content\/uploads\/2025\/09\/image-5.png\" alt=\"\" class=\"wp-image-1141\" srcset=\"https:\/\/jorgeturiel.es\/wp-content\/uploads\/2025\/09\/image-5.png 918w, https:\/\/jorgeturiel.es\/wp-content\/uploads\/2025\/09\/image-5-300x195.png 300w, https:\/\/jorgeturiel.es\/wp-content\/uploads\/2025\/09\/image-5-768x500.png 768w\" sizes=\"auto, (max-width: 918px) 100vw, 918px\" \/><figcaption class=\"wp-element-caption\">Campo de visi\u00f3n de la c\u00e1mara.<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Debemos obtener el ancho del campo de visi\u00f3n de esta manera:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" >ViewWidth := ViewPort1.Camera.Orthographic.EffectiveRect.Width; <\/pre><\/div>\n\n\n\n<p>Recuerda definir es la parte <em>published<\/em> de la unidad la variable <em>ViewPort1<\/em> para acceder al componente <em>ViewPort1<\/em> del editor.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" >published\n    { Components designed using CGE editor.\n      These fields will be automatically initialized at Start. }\n    LabelFps: TCastleLabel;\n    Navigation2D1: TCastle2DNavigation;\n    Arthur: TCastleScene;\n    Suelo1: TCastleBox;\n    ViewPort1: TCastleViewport; \/\/Define esta variable <\/pre><\/div>\n\n\n\n<p>  Obtenemos la posici\u00f3n de la c\u00e1mara, centrada en el eje X, con respecto al personaje.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" > PosicionCamara := Vector2(Arthur.TranslationXY.X,\n Navigation2D1.Camera.TranslationXY.Y);<\/pre><\/div>\n\n\n\n<p>Y antes de asignar la nueva posici\u00f3n a la c\u00e1mara hacemos unas comprobaciones. Si la posici\u00f3n en el eje X, menos la mitad del ancho es menor del l\u00edmite izquierdo, cargamos la posici\u00f3n del l\u00edmite m\u00e1s la mitad del ancho del campo de visi\u00f3n. Hacemos lo mismo para el l\u00edmite izquierdo.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" > if PosicionCamara.X - (ViewWidth \/ 2) &lt; LevelBounds.Left then\n  begin\n    PosicionCamara.X := LevelBounds.Left + (ViewWidth \/ 2);\n  end;\n  if PosicionCamara.X + (ViewWidth \/ 2) &gt; LevelBounds.Right then\n  begin\n    PosicionCamara.X := LevelBounds.Right - (ViewWidth \/ 2);\n  end;<\/pre><\/div>\n\n\n\n<p>Y asignamos la nueva posici\u00f3n a la c\u00e1mara.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:default decode:true \" >Navigation2D1.Camera.TranslationXY := PosicionCamara; <\/pre><\/div>\n\n\n\n<p>Tienes todo el c\u00f3digo completo en el <a href=\"https:\/\/github.com\/Blueicaro\/GhostAndGoblins_CGE-.git\" target=\"_blank\" rel=\"noreferrer noopener\">Github<\/a>.<\/p>\n\n\n\n<p>Saludos.<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Para que la c\u00e1mara siga el personaje, debemos cambiar el enfoque en nuestro c\u00f3digo.Cuando se pulse una tecla, se desplazar\u00e1 el personaje, y despu\u00e9s situaremos la c\u00e1mara d\u00f3nde est\u00e9 el personaje.<\/p>\n","protected":false},"author":2,"featured_media":1147,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[57,56,61,48],"tags":[49,23,21,24],"class_list":["post-1126","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-castle-game-engine","category-cge","category-free-pascal","category-videojuegos","tag-castle-game-engine","tag-lazarus","tag-pascal","tag-programacion"],"_links":{"self":[{"href":"https:\/\/jorgeturiel.es\/index.php?rest_route=\/wp\/v2\/posts\/1126","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/jorgeturiel.es\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/jorgeturiel.es\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/jorgeturiel.es\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/jorgeturiel.es\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1126"}],"version-history":[{"count":14,"href":"https:\/\/jorgeturiel.es\/index.php?rest_route=\/wp\/v2\/posts\/1126\/revisions"}],"predecessor-version":[{"id":1146,"href":"https:\/\/jorgeturiel.es\/index.php?rest_route=\/wp\/v2\/posts\/1126\/revisions\/1146"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/jorgeturiel.es\/index.php?rest_route=\/wp\/v2\/media\/1147"}],"wp:attachment":[{"href":"https:\/\/jorgeturiel.es\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1126"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/jorgeturiel.es\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1126"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/jorgeturiel.es\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1126"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}