Desenvolver Jogos em DelphiX Passos • Adicionar o TDXDraw – Onde se passa toda a acção • Adicionar um TDXImageList – – – – Um por cada tipo de imagens(heroi, balas...) Adicionar as imagens aos Items Associar ao TDXDraw NOTA: ao adicionar uma imagem esta deve de ficar em TDIB • Adicionar um TDXWaveList – Para termos sons prontos a utilizar • Adicionar um TDXTimer: – Colocar a propriedade enabled a false – Definir o intervalo de disparo – É aqui que está o focus da questão!! Classe’s VS Actores • Construir Classes/UNIT’s para cada um dos actores do Jogo : type TBullet = class(TImageSprite) private {colocar aqui atributos do actor} public {overload dos métodos que desejarmos redefinir} procedure DoMove(moveCount:integer);override; procedure DoDraw();override; procedure DoCollision(Sprite:TSprite;varDone:Boolean);override; end; { utilizar class completion: ctrl^c } DXSprite Unit • Components – TDXSpriteEngine • Objects – TBackgroundSprite • Chips property can be specified • collision judgment can be set with the CollisionMapproperty – TImageSprite • Onde a imagem é mostrada • Atributos: – Image – imagem a mostrar • surface é Image.PatternSurfaces[AnimStart+AnimPos] – TSprite • Class abstracta que apresenta os métodos: – DoDraw – DoMove – DoColision – TSpriteEngine • Controlo os sprites a ele associados Implementar os métodos de DXDraw para gestão dos eventos • DXDrawInitialize {colocar o timer com 60 segs} dxtimer1.Interval := 1000 div 40; dxtimer1.enabled := true; {outras coisas que desejar iniciar} • DXDrawFinalize dxtimer1.enabled := true; Nota: as afectações ao Timer devem de ser feitas sempre nestes métodos por definição. TDXTimer – o responsável (1) • Este objecto é quem contém o código importante da aplicação, pois permite o evoluir do jogo: {se não conseguirmos desenhar, terminamos} if (dxdraw1.canDraw = false) then exit; {actualiza a situação de input nos estados} DXInput1.Update(); {desenhar o fundo: sem stretch} DXImageFundos.Items[0].Draw( dxdraw1.surface, 0, 0, 0); {colocar a superficie com cor} DXDraw1.Surface.Fill( dxdraw1.surface.colormatch( RGB(0,100, 200)) ); {actualizar o ecrã, limpando os objectos mortos} DXSpriteEngine1.Dead(); {chamada aos DoMove dos actores com um numero de pixels} DXSpriteEngine1.move(2); {desenhar os sprites na superficie de fundo} DXSpriteEngine1.Draw(); TDXTimer – o responsável (2) • O seguinte código também pode ser adicionado: {utilizado para mostrar os frames per segundo e valores de variaveis} with DXDraw1.Surface.canvas do begin Brush.Style := bsClear; Font.Color := clRed; Font.Size := 20; TextOut(0, 0, 'fps: ' + intToStr(DXTimer1.FrameRate)); Release(); {obrigatorio} end; • Nunca esquecer NO FIM: {nao aparece nada sem esta chamada} DXDraw1.flip(); Criação dos objectos • A criação é feita no Form onCreate event handler: {criar o jogo} theGame := TGame.Create(); theGame.sprite := dxSpriteEngine1.engine; theGame.spriteEngine := dxdraw1; theGame.input := dxInput1; theGame.sound := dxSound1; theGame.imageListFUndos := dxImageFundos; theGame.imageListWalls := DXImageListWall; theGame.imageListHeroi := DXImageListHeroi; theGame.waveListHeroi := DXWaveHeroi; theGame.pontos := 0; {criar os actores} // criar o Heroi theHeroi := THeroi.Create(theGame); • • theGame e theHeroi é um atributo da form1 Na criaçao do jogo faz-se sempre a associação entre os componentes DelphiX da form e a instância de TGame TImageSprite – atributos • TImageSprite – – – – – – – – AnimCount AnimLooped AnimPos AnimSpeed AnimStart Image PixelCheck Tile •Derived from TSprite –BoundsRect –ClientRect –Collisioned –Count –Engine –Height –Items –Moved –Parent –Visible –Width –WorldX –WorldY –X –Y –Z Um Actor • Um Actor encontra-se associado a um jogo, logo para se construir um novo Actor deriva-se da classe TActor: type TActor = class (TImageSprite) protected game: TGame; public {apresenta um contructor apenas} constructor Create(game: TGame); end; implementation constructor TActor.Create(game: TGame); begin inherited Create(game.sprite); {associa o sprite} self.game := game; {associa-se ao jogo} self.z := 10; {atribui um valor por omissão ao Z} end; Dica • Não esquecer de fazer a destruição de todos os actores e game no form onDestroy event handler: theGame.free(); theGun.free(); TGame • O Tipo TGame representa o Jogo • Existe para simplificar e aproveitar as capacidades drag-drop do Delphi • Difere de jogo para jogo • Apresenta como atributos : – tudo o que representa o jogo • (e.g.) score, vidas, tempo – os elementos DelphiX utilizados • (e.g.) TDXWaveList’s, TDXImageList’s, TDXInput... • Os atributos não são privados o que viola as regras de OOP, no entanto foi o melhor possível... • Os atributos são afectados no evento de Create da form que contém os componentes. (e.g) TGame type TGame = class // o que é indispensável {sempre} sprite : TSprite; spriteEngine: TDXDraw; input: TDXInput; sound: TDXSound; // imageLists necessarias imageListFUndos: TDXImageList; imageListWalls: TDXImageList; imageListHeroi: TDXImageList; // wavelists necessarias waveListHeroi: TDXWaveList; // atributos necessarios ao Jogo pontos: integer; vidas: integer; end; Definição de um novo Actor Derivar da classe TActor que deriva de TImageSprite: THeroi = class(TActor) private left, right, up, down: boolean; public constructor Create(game:TGame); procedure DoDraw();override; procedure DoMove(moveCount:integer);override; procedure DoCollision(sprite: TSprite; var Done:boolean);override; end; • • • Redefinir os métodos em que estamos interessados.(exemplos de seguida) Nota: os atributos que se encontram apresentados são específicos deste Tipo Nota: cada Actor deve de existir numa única UNIT: encapsulamento. O Constructor • A cada Actor está associado sempre um Game que é afectado no Constructor – método Create(..): constructor THeroi.Create(game: TGame); begin inherited Create(game); {obrigatório} self.Image := self.game.imageListHeroi.Items[0]; self.Width := self.Image.Width; self.height := self.Image.Height; self.x := self.game.spriteEngine.Width div 2; self.y := self.game.spriteEngine.height (2*self.Image.Height); left := false; right := false; up := false; down := false; end; • É no constructor que se afectam os atributos que representam o Actor Os Intervenientes Movimento dos Objectos • A redefinição do método onMove que é responsável pelo movimento: procedure THeroi.DoMove (moveCount: integer); begin Pode ser inherited DoMove(moveCount); utilizado para if(isLeft in self.game.input.States) then mexer todos os actores de igual self.X := self.X - moveCount; forma. if(isRight in self.game.input.States) then É afectado no self.X := self.X + moveCount; evento de if(isUp in self.game.input.States)then DXTimer construção do self.y := self.y - moveCount; if(isDown in self.game.input.States) then TGame self.y := self.y + moveCount; collision(); {para testar se houve colisões} end; BMP’s especiais – animação do Heroi // animar o heroi self.AnimPos := 0; self.AnimStart := 20; self.AnimCount := 6; self.AnimSpeed := 5/1000; self.AnimLooped := true; Nota: as propriedades ParentHeight e ParentWidth têm que ser alterardas para representar o tamanho do Heroi Choque – detecção de colisões? • Sempre que é feito um movimento verifica-se se houve choque com outro actor de um tipo especificado: procedure THeroi.DoCollision(sprite: TSprite; var Done: boolean); begin inherited; if (sprite is TWall) then begin self.game.waveListHeroi.items[0].play(false); {gritar!!??} end; end; Acesso a imagens ou sons • Todos os TActor estão associados um game que conhece: type TGame = class // o que é indispensável {sempre} // imageLists necessarias imageListFUndos: TDXImageList; imageListWalls: TDXImageList; imageListHeroi: TDXImageList; // wavelists necessarias waveListHeroi: TDXWaveList; // atributos necessarios ao Jogo pontos: integer; vidas : integer: False: nao espera terminar o som True: bloqueia • Logo o acesso é feito da seguinte forma: self.game.waveListHeroi.items[0].play(false); self.game.imageListHeroi.Items[0]; {[‘nomeImagem’]} Exemplo de um movimento pseudo-aleatorio // a utilizar no DoMove if(random(30)=1) then begin self.Image := game.DXImageLaden.items[random(2)]; self.x := random(game.dxdraw1.Width); self.y := random(game.dxdraw1.height-self.Image.Height); end; Não esquecer da chamada á função randomize(); no evento de criação da Form que contém o jogo. Carregar paredes (leitura da informação de um Ficheiro) assignFile(fich, 'level1.txt'); reset(fich); cnt:=0; while (not eof(fich)) do begin read(fich, ch); if(not ((ch = #13) or (ch=#10)))then begin aux[cnt] := ch; cnt := cnt + 1; end; end; cnt:=0; for yy:=0 to 6 do for xx:=0 to 9 do begin if(aux[cnt]='0') then TWall.Create(theGame, xx, yy); cnt := cnt + 1; end; var xx, yy: integer; fich: file of char; aux : array[0..100] of char; cnt: integer; ch: char; Opções de Projecto – em Delphi Project --> Options --> application Pode-se alterar várias definições: – icon – title – Help file