En esta entrada aprenderemos a aplicar traslaciones objetos y rotaciones, no solo desde en entorno de Castle Game Engine, sino que lo haremos desde código.
Contenido
Preparando la escena.
Vamos a preparar una escena en tres dimensiones. Si tienes dudas de como empezar con Castle Game Engine, puede visitar esta entrada del blog.
Crea una escena 3D, y añade un rectángulo en el centro, modifica las dimensiones del rectángulo para su tamaño en X sea igual a uno, en Y sea igual a siete y en Z sea igual a uno.
Ahora modifica su posición, para ello cambia la propiedad translation del rectángulo que acabas de crear. Y ajusta su valor Y a tres y medio.
Acabas de realizar tu primera traslación. Pero lo interesante sería realizar desde código, respondiendo a la pulsación de unas teclas.
Usando los componentes en el código
Para ello, lo primero es añadir el rectángulo al código. Abre el editor de código pulsando F12, y añade la clausula CastleScene en la sección uses. Y el en la sección published añade una variable llamada Box1, del tipo TCastleBox
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
uses Classes, CastleVectors, CastleComponentSerialize, CastleUIControls, CastleControls, CastleKeysMouse, CastleScene, CastleLog, CastleWindow; { Main view, where most of the application logic takes place. } TViewMain = class(TCastleView) private Origen: TVector3; Centro: TVector3; Rotacion: TVector4; published { Components designed using CGE editor.< These fields will be automatically initialized at Start. } LabelFps: TCastleLabel; Box1: TCastleBox; //añadimos variable tipo TcastleBox (...) |
El siguiente paso es asignar el rectángulo que tenemos en el editor, a la variable Box1, que acabamos de crear. Esto se hace en el procedimiento Start, tal como se muestra en el código más abajo1.
1 2 3 4 5 6 7 8 |
procedure TViewMain.Start; begin inherited; {En algunos casos no es necesario, ver comentario de Michail al final de la entrada Box1 := DesignedComponent('Box1') as TCastleBox; } end; |
Observa, que la variable que creamos anteriormente, le asignamos un el componente Box1 (que es como se llama en el editor), y le indicamos que es de tipo TCastleBox.
Traslaciones por código
Ahora, solo tenemos que ir al procedimiento Press, y añadir el el siguiente código.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
function TViewMain.Press(const Event: TInputPressRelease): boolean; var posicion: TVector3; begin Result := inherited; if Result then Exit; // allow the ancestor to handle keys { This virtual method is executed when user presses a key, a mouse button, or touches a touch-screen. Note that each UI control has also events like OnPress and OnClick. These events can be used to handle the "press", if it should do something specific when used in that UI control. The TViewMain.Press method should be used to handle keys not handled in children controls. } // Use this to handle keys: if Event.IsKey(keyL) then begin Box1.Translation := Box1.Translation + Vector3(0.5, 0, 0); Exit(True); end; if Event.IsKey(keyj) then begin Box1.Translation := Box1.Translation + Vector3(-0.5, 0, 0); Exit(True); end; end; |
Observa, que para desplazar el rectángulo, simplemente sumamos un nuevo vector a su propiedad Translation. El motivo de sumar un vector, es que los objetos está situados en el espacio, y tiene tres coordenadas. En este caso se le suma 0.5 unidades en la coordenada X.
Rotaciones por código.
Ahora, podemos aplicar una rotación, podemos hacer los mismo que con la traslación, sumar a la rotación actual una nueva rotación, pero ello usaremos la propiedad Rotation. Observa este código.
1 |
box1.Rotation := Box1.Rotation + Vector4(0, 0, 1, -DegToRad(1)); |
En este caso, la rotación se expresa por un vector de cuatro componentes. Los tres primeros indican los ejes a los que se le aplica la rotación. Si el valor de un eje determinada es uno, se le aplicará una rotación, cuyo valor es indicado en el último componente del vector. Este valor es indicado en radianes. Como es más cómodo o intuitivo trabajar con grados, el valor es convertido de grados a radianes.
La rotación de un elemento se produce desde dónde está su centro. Observa el rectángulo en el editor. En esta imagen se puede ver el punto de rotación, indicado por un eje de coordenadas.
Si deseamos que la rotación se realice desde otro punto diferente, debemos desplazar el el centro del objeto a dónde nos interese. En el este ejemplo, la rotación del rectángulo se produce desde su punto de apoyo.
Observa el vídeo.
Aquí tiene el código fuente con algún detalle más. Si se pulsa la tecla retroceso, el rectángulo vuelve a su origen. Así mismo si se pulsa la tecla escape se termina la aplicación.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
{ Main view, where most of the application logic takes place. Feel free to use this code as a starting point for your own projects. This template code is in public domain, unlike most other CGE code which is covered by BSD or LGPL (see https://castle-engine.io/license). } unit GameViewMain; interface uses Classes, CastleVectors, CastleComponentSerialize, CastleUIControls, CastleControls, CastleKeysMouse, CastleScene, CastleLog, CastleWindow; type { Main view, where most of the application logic takes place. } TViewMain = class(TCastleView) private Origen: TVector3; Centro: TVector3; Rotacion: TVector4; published { Components designed using CGE editor. These fields will be automatically initialized at Start. } LabelFps: TCastleLabel; Box1: TCastleBox; public constructor Create(AOwner: TComponent); override; procedure Start; override; procedure Update(const SecondsPassed: single; var HandleInput: boolean); override; function Press(const Event: TInputPressRelease): boolean; override; end; var ViewMain: TViewMain; implementation uses SysUtils, Math; { TViewMain ----------------------------------------------------------------- } constructor TViewMain.Create(AOwner: TComponent); begin inherited; DesignUrl := 'castle-data:/gameviewmain.castle-user-interface'; end; procedure TViewMain.Start; begin inherited; Box1 := DesignedComponent('Box1') as TCastleBox; Origen := Box1.Translation; Rotacion := Box1.Rotation; Box1.Center := Vector3(0, -Box1.Size.Y / 2, 0); end; procedure TViewMain.Update(const SecondsPassed: single; var HandleInput: boolean); begin inherited; { This virtual method is executed every frame (many times per second). } Assert(LabelFps <> nil, 'If you remove LabelFps from the design, remember to remove also the assignment "LabelFps.Caption := ..." from code'); LabelFps.Caption := 'FPS: ' + Container.Fps.ToString; end; function TViewMain.Press(const Event: TInputPressRelease): boolean; var posicion: TVector3; begin Result := inherited; if Result then Exit; // allow the ancestor to handle keys { This virtual method is executed when user presses a key, a mouse button, or touches a touch-screen. Note that each UI control has also events like OnPress and OnClick. These events can be used to handle the "press", if it should do something specific when used in that UI control. The TViewMain.Press method should be used to handle keys not handled in children controls. } // Use this to handle keys: if Event.IsKey(keyL) then begin Box1.Translation := Box1.Translation + Vector3(0.5, 0, 0); Exit(True); end; if Event.IsKey(keyj) then begin Box1.Translation := Box1.Translation + Vector3(-0.5, 0, 0); Exit(True); end; if Event.IsKey(keyBackSpace) then begin Box1.Translation := Origen; Box1.Rotation := Vector4(0, 0, 0, 1); Exit(True); end; if Event.IsKey(keyA) then begin if box1.Rotation.W <= 0 then begin Box1.Center := Vector3(box1.Size.x / 2, -Box1.Size.Y / 2, 0); end else begin Box1.Center := Vector3(-box1.Size.x / 2, -Box1.Size.Y / 2, 0); end; box1.Rotation := Box1.Rotation + Vector4(0, 0, 1, DegToRad(1)); WritelnLog('Box1', Box1.Rotation.ToString); WritelnLog('Center', Box1.Center.ToString); Exit(True); end; if Event.IsKey(keyD) then begin if box1.Rotation.W <= 0 then begin Box1.Center := Vector3(box1.Size.x / 2, -Box1.Size.Y / 2, 0); end else begin Box1.Center := Vector3(-box1.Size.x / 2, -Box1.Size.Y / 2, 0); end; box1.Rotation := Box1.Rotation + Vector4(0, 0, 1, -DegToRad(1)); WritelnLog('Box1', Box1.Rotation.ToString); WritelnLog('Center', Box1.Center.ToString); Exit(True); end; if Event.IsKey(keyEscape) then begin Application.Terminate; Exit(true); end; end; end. |
Conclusiones.
En esta entrada hemos visto:
- Cómo usar los objetos definidos en el editor desde código.
- Cómo trasladar objetos desde código.
- Cómo rotar objetos desde código.
Saludos
- Editada la entrada. No siempre es necesario realizar este paso. Según el comentario Michalis, para más información visitar: https://castle-engine.io/wp/2022/10/16/published-state-fields-are-now-automatically-initialized-no-need-in-most-cases-for-designedcomponent-calls/ ↩︎
Thank you for the article! I noticed one small possible improvement:
You should not need to use the line
«»»
Box1 := DesignedComponent(‘Box1’) as TCastleBox;
«»»
in TViewMain.Start . You can actually just remove it, and the example will work the same 🙂 That’s because the «Box1: TCastleBox» is already declared in the «published» section of the view, and so at «Start» it will be automatically set to point to the «Box1» component in your design.
See https://castle-engine.io/wp/2022/10/16/published-state-fields-are-now-automatically-initialized-no-need-in-most-cases-for-designedcomponent-calls/ .
Thanks for the improvement. I add a note where I explain your comment.