System.Drawing.Bitmap
) load the texture into memory, get a binary bitmap from it and transfer it to OpenGL, which will save the texture already in its memory. In the future, the texture can be accessed through an integer handle (which must first be reserved).Texture
. using System; using System.Drawing; using System.Drawing.Imaging; using OpenTK.Graphics.OpenGL; namespace ImpressiveSolids { public class Texture : IDisposable { public int GlHandle { get; protected set; } public int Width { get; protected set; } public int Height { get; protected set; } public Texture(Bitmap Bitmap) { GlHandle = GL.GenTexture(); Bind(); Width = Bitmap.Width; Height = Bitmap.Height; var BitmapData = Bitmap.LockBits(new Rectangle(0, 0, Bitmap.Width, Bitmap.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, BitmapData.Width, BitmapData.Height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, BitmapData.Scan0); Bitmap.UnlockBits(BitmapData); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); } public void Bind() { GL.BindTexture(TextureTarget.Texture2D, GlHandle); } #region Disposable private bool Disposed = false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool Disposing) { if (!Disposed) { if (Disposing) { GL.DeleteTexture(GlHandle); } Disposed = true; } } ~Texture() { Dispose(false); } #endregion } }
Game
will load the textures. using System.Drawing; // . . . private Texture TextureBackground; private Texture[] ColorTextures = new Texture[ColorsCount]; public Game() : base(NominalWidth, NominalHeight, GraphicsMode.Default, "Impressive Solids") { VSync = VSyncMode.On; Keyboard.KeyDown += new EventHandler<KeyboardKeyEventArgs>(OnKeyDown); TextureBackground = new Texture(new Bitmap("textures/background.png")); for (var i = 0; i < ColorsCount; i++) { ColorTextures[i] = new Texture(new Bitmap("textures/solids/" + i + ".png")); } }
protected override void OnLoad(EventArgs E) { base.OnLoad(E); GL.Enable(EnableCap.Texture2D); GL.Enable(EnableCap.Blend); GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); New(); } protected override void OnRenderFrame(FrameEventArgs E) { // . . . GL.LoadMatrix(ref Modelview); RenderBackground(); for (var X = 0; X < MapWidth; X++) { for (var Y = 0; Y < MapHeight; Y++) { if (Map[X, Y] >= 0) { RenderSolid(X, Y + ImpactFallOffset[X, Y], Map[X, Y]); } } } if (GameStateEnum.Fall == GameState) { for (var i = 0; i < StickLength; i++) { RenderSolid(StickPosition.X + i, StickPosition.Y, StickColors[i]); } } SwapBuffers(); } private void RenderBackground() { TextureBackground.Bind(); GL.Color4(Color4.White); GL.Begin(BeginMode.Quads); GL.TexCoord2(0, 0); GL.Vertex2(0, 0); GL.TexCoord2((float)ClientRectangle.Width / TextureBackground.Width, 0); GL.Vertex2(ProjectionWidth, 0); GL.TexCoord2((float)ClientRectangle.Width / TextureBackground.Width, (float)ClientRectangle.Height / TextureBackground.Height); GL.Vertex2(ProjectionWidth, ProjectionHeight); GL.TexCoord2(0, (float)ClientRectangle.Height / TextureBackground.Height); GL.Vertex2(0, ProjectionHeight); GL.End(); } private void RenderSolid(float X, float Y, int Color) { ColorTextures[Color].Bind(); GL.Color4(Color4.White); GL.Begin(BeginMode.Quads); GL.TexCoord2(0, 0); GL.Vertex2(X * SolidSize, Y * SolidSize); GL.TexCoord2(1, 0); GL.Vertex2((X + 1) * SolidSize, Y * SolidSize); GL.TexCoord2(1, 1); GL.Vertex2((X + 1) * SolidSize, (Y + 1) * SolidSize); GL.TexCoord2(0, 1); GL.Vertex2(X * SolidSize, (Y + 1) * SolidSize); GL.End(); }
private void RenderPipe() { GL.Disable(EnableCap.Texture2D); GL.Color4(Color4.Black); GL.Begin(BeginMode.Quads); GL.Vertex2(0, 0); GL.Vertex2(MapWidth * SolidSize, 0); GL.Vertex2(MapWidth * SolidSize, MapHeight * SolidSize); GL.Vertex2(0, MapHeight * SolidSize); GL.End(); GL.Enable(EnableCap.Texture2D); } protected override void OnRenderFrame(FrameEventArgs E) { // . . . RenderBackground(); RenderPipe(); // . . . }
NominalWidth
making the window smaller than NominalWidth
Ă— NominalHeight
(this, however, will not work under the X window system). private const int NominalWidth = 500; private const int NominalHeight = 500; protected override void OnResize(EventArgs E) { // . . . if (ClientSize.Width < NominalWidth) { ClientSize = new Size(NominalWidth, ClientSize.Height); } if (ClientSize.Height < NominalHeight) { ClientSize = new Size(ClientSize.Width, NominalHeight); } } protected override void OnRenderFrame(FrameEventArgs E) { // . . . RenderBackground(); var PipeMarginY = (ProjectionHeight - MapHeight * SolidSize) / 2f; var PipeMarginX = (NominalHeight - MapHeight * SolidSize) / 2f; var Overwidth = ProjectionWidth - ProjectionHeight * (float)NominalWidth / NominalHeight; if (Overwidth > 0) { GL.Translate(Math.Min(Overwidth, (ProjectionWidth - MapWidth * SolidSize) / 2f), PipeMarginY, 0); } else { GL.Translate(PipeMarginX, PipeMarginY, 0); } RenderPipe(); // . . . }
TextPrinter
for this purpose. It was a long time and not true. Now the recommended method for displaying text is the following: make a bitmap with text (using System.Drawing.Graphics.DrawString
or others) and drag it as a texture.TextRenderer
class, which will create a Bitmap
and then Texture
based on it. But first you have to attend to the aforementioned problem of NPOT-dimensional textures, since we do not know in advance what size the dynamically created label will have. The method is quite simple: if NPOT-textures are not supported, then when loading a picture, you need to make a POT-dimensional texture, as if with fields. For example, if we load a 300 Ă— 200 image, then we generate a 512 Ă— 256 texture, on which our image will be in the upper left corner, and the rest of the space will be empty. And when applying a texture, it will be necessary to take into account that the lower left corner of the picture has the coordinates not (1; 1), but (300/512; 200/256). public class Texture : IDisposable { public int GlHandle { get; protected set; } public int Width { get; protected set; } public int Height { get; protected set; } #region NPOT private static bool? CalculatedSupportForNpot; public static bool NpotIsSupported { get { if (!CalculatedSupportForNpot.HasValue) { CalculatedSupportForNpot = false; int ExtensionsCount; GL.GetInteger(GetPName.NumExtensions, out ExtensionsCount); for (var i = 0; i < ExtensionsCount; i++) { if ("GL_ARB_texture_non_power_of_two" == GL.GetString(StringName.Extensions, i)) { CalculatedSupportForNpot = true; break; } } } return CalculatedSupportForNpot.Value; } } public int PotWidth { get { return NpotIsSupported ? Width : (int)Math.Pow(2, Math.Ceiling(Math.Log(Width, 2))); } } public int PotHeight { get { return NpotIsSupported ? Height : (int)Math.Pow(2, Math.Ceiling(Math.Log(Height, 2))); } } #endregion public Texture(Bitmap Bitmap) { // . . . var BitmapData = Bitmap.LockBits(new Rectangle(0, 0, Bitmap.Width, Bitmap.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, PotWidth, PotHeight, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero); GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, BitmapData.Width, BitmapData.Height, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, BitmapData.Scan0); Bitmap.UnlockBits(BitmapData); // . . . } // . . . }
TextRenderer
. The code seems long, but actually everything is simple. Here we do four main things: we set the parameters of the text, measure its dimensions, draw the text into the texture and, finally, apply the texture to the transparent rectangle. using System; using System.Drawing; using System.Drawing.Text; using OpenTK.Graphics; using OpenTK.Graphics.OpenGL; namespace ImpressiveSolids { class TextRenderer { private Font FontValue; private string LabelValue; private bool NeedToCalculateSize, NeedToRenderTexture; private Texture Texture; private int CalculatedWidth, CalculatedHeight; public Font Font { get { return FontValue; } set { FontValue = value; NeedToCalculateSize = true; NeedToRenderTexture = true; } } public string Label { get { return LabelValue; } set { if (value != LabelValue) { LabelValue = value; NeedToCalculateSize = true; NeedToRenderTexture = true; } } } public int Width { get { if (NeedToCalculateSize) { CalculateSize(); } return CalculatedWidth; } } public int Height { get { if (NeedToCalculateSize) { CalculateSize(); } return CalculatedHeight; } } public Color4 Color = Color4.Black; public TextRenderer(Font Font) { this.Font = Font; } public TextRenderer(Font Font, Color4 Color) { this.Font = Font; this.Color = Color; } public TextRenderer(Font Font, string Label) { this.Font = Font; this.Label = Label; } public TextRenderer(Font Font, Color4 Color, string Label) { this.Font = Font; this.Color = Color; this.Label = Label; } private void CalculateSize() { using (var Bitmap = new Bitmap(1, 1)) { using (Graphics Graphics = Graphics.FromImage(Bitmap)) { var Measures = Graphics.MeasureString(Label, Font); CalculatedWidth = (int)Math.Ceiling(Measures.Width); CalculatedHeight = (int)Math.Ceiling(Measures.Height); } } NeedToCalculateSize = false; } public void Render() { if ((null == Label) || ("" == Label)) { return; } if (NeedToRenderTexture) { using (var Bitmap = new Bitmap(Width, Height)) { var Rectangle = new Rectangle(0, 0, Bitmap.Width, Bitmap.Height); using (Graphics Graphics = Graphics.FromImage(Bitmap)) { Graphics.Clear(System.Drawing.Color.Transparent); Graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit; Graphics.DrawString(Label, Font, Brushes.White, Rectangle); if (null != Texture) { Texture.Dispose(); } Texture = new Texture(Bitmap); } } NeedToRenderTexture = false; } Texture.Bind(); GL.Color4(Color); GL.Begin(BeginMode.Quads); GL.TexCoord2(0, 0); GL.Vertex2(0, 0); GL.TexCoord2((float)Texture.Width / Texture.PotWidth, 0); GL.Vertex2(Width, 0); GL.TexCoord2((float)Texture.Width / Texture.PotWidth, (float)Texture.Height / Texture.PotHeight); GL.Vertex2(Width, Height); GL.TexCoord2(0, (float)Texture.Height / Texture.PotHeight); GL.Vertex2(0, Height); GL.End(); } } }
using System.Drawing.Text; // . . . private int Score; private int HighScore; private TextRenderer NextStickLabel, ScoreLabel, ScoreRenderer, HighScoreLabel, HighScoreRenderer, GameOverLabel, GameOverHint; public Game() // . . . var LabelFont = new Font(new FontFamily(GenericFontFamilies.SansSerif), 20, GraphicsUnit.Pixel); var LabelColor = Color4.SteelBlue; NextStickLabel = new TextRenderer(LabelFont, LabelColor, "Next"); ScoreLabel = new TextRenderer(LabelFont, LabelColor, "Score"); HighScoreLabel = new TextRenderer(LabelFont, LabelColor, "High score"); var ScoreFont = new Font(new FontFamily(GenericFontFamilies.SansSerif), 50, GraphicsUnit.Pixel); var ScoreColor = Color4.Tomato; ScoreRenderer = new TextRenderer(ScoreFont, ScoreColor); HighScoreRenderer = new TextRenderer(ScoreFont, ScoreColor); var GameStateFont = new Font(new FontFamily(GenericFontFamilies.SansSerif), 30, GraphicsUnit.Pixel); var GameStateColor = Color4.Tomato; GameOverLabel = new TextRenderer(GameStateFont, GameStateColor, "Game over"); var GameStateHintFont = new Font(new FontFamily(GenericFontFamilies.SansSerif), 25, GraphicsUnit.Pixel); var GameStateHintColor = Color4.SteelBlue; GameOverHint = new TextRenderer(GameStateHintFont, GameStateHintColor, "Press Enter"); } protected override void OnRenderFrame(FrameEventArgs E) { // . . . GL.Translate(MapWidth * SolidSize + PipeMarginX, 0, 0); NextStickLabel.Render(); // TODO next stick GL.Translate(0, MapHeight * SolidSize / 4f, 0); if (GameStateEnum.GameOver == GameState) { GameOverLabel.Render(); GL.Translate(0, GameOverLabel.Height, 0); GameOverHint.Render(); GL.Translate(0, -GameOverLabel.Height, 0); } GL.Translate(0, MapHeight * SolidSize / 4f, 0); ScoreLabel.Render(); GL.Translate(0, ScoreLabel.Height, 0); ScoreRenderer.Label = Score.ToString(); ScoreRenderer.Render(); GL.Translate(0, -ScoreLabel.Height, 0); GL.Translate(0, MapHeight * SolidSize / 4f, 0); HighScoreLabel.Render(); GL.Translate(0, HighScoreLabel.Height, 0); HighScoreRenderer.Label = HighScore.ToString(); HighScoreRenderer.Render(); SwapBuffers(); }
MapHeight * SolidSize / 4f
is a quarter of the height of the glass, each time we descend below this distance to depict one of the four elements of the interface. In addition, removing the inscription, we descend to its height below, and then do not forget to rise back to the starting point. private int[] NextStickColors; private void GenerateNextStick() { for (var i = 0; i < StickLength; i++) { StickColors[i] = NextStickColors[i]; NextStickColors[i] = Rand.Next(ColorsCount); } StickPosition.X = (float)Math.Floor((MapWidth - StickLength) / 2d); StickPosition.Y = 0; } private void New() { // . . . StickColors = new int[StickLength]; NextStickColors = new int[StickLength]; GenerateNextStick(); GenerateNextStick(); // because 1st call makes current stick all zeros GameState = GameStateEnum.Fall; }
RenderSolid
method, everything is very simple. protected override void OnRenderFrame(FrameEventArgs E) { // . . . NextStickLabel.Render(); GL.Translate(0, NextStickLabel.Height, 0); RenderNextStick(); GL.Translate(0, -NextStickLabel.Height, 0); // . . . } public void RenderNextStick() { GL.Disable(EnableCap.Texture2D); GL.Color4(Color4.Black); GL.Begin(BeginMode.Quads); GL.Vertex2(0, 0); GL.Vertex2(StickLength * SolidSize, 0); GL.Vertex2(StickLength * SolidSize, SolidSize); GL.Vertex2(0, SolidSize); GL.End(); GL.Enable(EnableCap.Texture2D); for (var i = 0; i < StickLength; i++) { RenderSolid(i, 0, NextStickColors[i]); } }
private int TotalDestroyedThisMove; private void New() { // . . . Score = 0; TotalDestroyedThisMove = 0; } protected override void OnUpdateFrame(FrameEventArgs E) { // . . . if (Destroyables.Count > 0) { foreach (var Coords in Destroyables) { Map[(int)Coords.X, (int)Coords.Y] = -1; } Score += (int)Math.Ceiling(Destroyables.Count + Math.Pow(1.5, Destroyables.Count - 3) - 1) + TotalDestroyedThisMove; TotalDestroyedThisMove += Destroyables.Count; Stabilized = false; } // . . . GenerateNextStick(); TotalDestroyedThisMove = 0; GameState = GameStateEnum.Fall; // . . . }
using System.IO; // . . . private string HighScoreFilename; public Game() { // . . . var ConfigDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + Path.DirectorySeparatorChar + "ImpressiveSolids"; if (!Directory.Exists(ConfigDirectory)) { Directory.CreateDirectory(ConfigDirectory); } HighScoreFilename = ConfigDirectory + Path.DirectorySeparatorChar + "HighScore.dat"; if (File.Exists(HighScoreFilename)) { using (var Stream = new FileStream(HighScoreFilename, FileMode.Open)) { using (var Reader = new BinaryReader(Stream)) { try { HighScore = Reader.ReadInt32(); } catch (IOException) { HighScore = 0; } } } } else { HighScore = 0; } } protected override void OnUpdateFrame(FrameEventArgs E) { // . . . if (GameOver) { GameState = GameStateEnum.GameOver; if (Score > HighScore) { HighScore = Score; using (var Stream = new FileStream(HighScoreFilename, FileMode.Create)) { using (var Writer = new BinaryWriter(Stream)) { Writer.Write(HighScore); } } } } else { // . . . }
GameStateEnum
), because the game can be paused both during the fall of the stick ( Fall
) and during Impact
, and from the pause the game should return to the state it was in.Paused
flag and its processing in OnUpdateFrame
, OnKeyDown
, OnRenderFrame
. private bool Paused; private TextRenderer PauseLabel, UnpauseHint, PlayingGameLabel, PauseHint; public Game() // . . . var GameStateFont = new Font(new FontFamily(GenericFontFamilies.SansSerif), 30, GraphicsUnit.Pixel); var GameStateColor = Color4.Tomato; GameOverLabel = new TextRenderer(GameStateFont, GameStateColor, "Game over"); PauseLabel = new TextRenderer(GameStateFont, GameStateColor, "Pause"); PlayingGameLabel = new TextRenderer(GameStateFont, GameStateColor, "Playing"); var GameStateHintFont = new Font(new FontFamily(GenericFontFamilies.SansSerif), 25, GraphicsUnit.Pixel); var GameStateHintColor = Color4.SteelBlue; GameOverHint = new TextRenderer(GameStateHintFont, GameStateHintColor, "Press Enter"); UnpauseHint = new TextRenderer(GameStateHintFont, GameStateHintColor, "Press Space"); PauseHint = new TextRenderer(GameStateHintFont, GameStateHintColor, "Space pauses"); } protected override void OnLoad(EventArgs E) { base.OnLoad(E); GL.Enable(EnableCap.Texture2D); GL.Enable(EnableCap.Blend); GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); New(); Paused = true; } protected override void OnUpdateFrame(FrameEventArgs E) { base.OnUpdateFrame(E); if (Paused) { return; } // . . . } protected void OnKeyDown(object Sender, KeyboardKeyEventArgs E) { if ((GameStateEnum.Fall == GameState) && !Paused) { // . . . } if (((GameStateEnum.Fall == GameState) || (GameStateEnum.Impact == GameState)) && (Key.Space == E.Key)) { Paused = !Paused; } } protected override void OnRenderFrame(FrameEventArgs E) { // . . . GL.Translate(0, MapHeight * SolidSize / 4f, 0); if (GameStateEnum.GameOver == GameState) { GameOverLabel.Render(); GL.Translate(0, GameOverLabel.Height, 0); GameOverHint.Render(); GL.Translate(0, -GameOverLabel.Height, 0); } else if (Paused) { PauseLabel.Render(); GL.Translate(0, PauseLabel.Height, 0); UnpauseHint.Render(); GL.Translate(0, -PauseLabel.Height, 0); } else { PlayingGameLabel.Render(); GL.Translate(0, PlayingGameLabel.Height, 0); PauseHint.Render(); GL.Translate(0, -PlayingGameLabel.Height, 0); } // . . . }
Source: https://habr.com/ru/post/134283/
All Articles